Blob Blame History Raw
From bfe57d20e9d39d52428e95e493d9af0bd034a82f Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 15 Jan 2018 14:44:07 +0900
Subject: [PATCH] Added DBus filtering against malware

The proposal prevents non-ower of the GDBusConnection from accessing
DBus methods against malicious usages.

BUG=https://github.com/ibus/ibus/issues/1955

Review URL: https://codereview.appspot.com/335380043
---
 bus/inputcontext.c     | 24 +++++++++++++++++++++++-
 src/ibusengine.c       | 18 +++++++++++++++++-
 src/ibuspanelservice.c | 14 +++++++++++++-
 3 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/bus/inputcontext.c b/bus/inputcontext.c
index d8be9e3f..4f2ecafc 100644
--- a/bus/inputcontext.c
+++ b/bus/inputcontext.c
@@ -2,7 +2,7 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2014 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2008-2016 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -1148,6 +1148,20 @@ _ic_set_surrounding_text (BusInputContext       *context,
     g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
+/*
+ * Since IBusService is inherited by IBusImpl, this method cannot be
+ * applied to IBusServiceClass.method_call() directly but can be in
+ * each child class.method_call().
+ */
+static gboolean
+bus_input_context_service_authorized_method (IBusService     *service,
+                                             GDBusConnection *connection)
+{
+    if (ibus_service_get_connection (service) == connection)
+        return TRUE;
+    return FALSE;
+}
+
 /**
  * bus_input_context_service_method_call:
  *
@@ -1197,6 +1211,10 @@ bus_input_context_service_method_call (IBusService            *service,
     };
 
     gint i;
+
+    if (!bus_input_context_service_authorized_method (service, connection))
+        return;
+
     for (i = 0; i < G_N_ELEMENTS (methods); i++) {
         if (g_strcmp0 (method_name, methods[i].method_name) == 0) {
             methods[i].method_callback ((BusInputContext *)service, parameters, invocation);
@@ -1270,6 +1288,9 @@ bus_input_context_service_set_property (IBusService     *service,
                                   error);
     }
 
+    if (!bus_input_context_service_authorized_method (service, connection))
+        return FALSE;
+
     if (g_strcmp0 (property_name, "ContentType") == 0) {
         BusInputContext *context = (BusInputContext *) service;
         _ic_set_content_type (context, value);
@@ -1279,6 +1300,7 @@ bus_input_context_service_set_property (IBusService     *service,
     g_return_val_if_reached (FALSE);
 }
 
+
 gboolean
 bus_input_context_has_focus (BusInputContext *context)
 {
diff --git a/src/ibusengine.c b/src/ibusengine.c
index b2a8022a..da648d11 100644
--- a/src/ibusengine.c
+++ b/src/ibusengine.c
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -851,6 +852,15 @@ ibus_engine_get_property (IBusEngine *engine,
     }
 }
 
+static gboolean
+ibus_engine_service_authorized_method (IBusService     *service,
+                                       GDBusConnection *connection)
+{
+    if (ibus_service_get_connection (service) == connection)
+        return TRUE;
+    return FALSE;
+}
+
 static void
 ibus_engine_service_method_call (IBusService           *service,
                                  GDBusConnection       *connection,
@@ -876,6 +886,9 @@ ibus_engine_service_method_call (IBusService           *service,
         return;
     }
 
+    if (!ibus_engine_service_authorized_method (service, connection))
+        return;
+
     if (g_strcmp0 (method_name, "ProcessKeyEvent") == 0) {
         guint keyval, keycode, state;
         gboolean retval = FALSE;
@@ -1085,6 +1098,9 @@ ibus_engine_service_set_property (IBusService        *service,
                                   error);
     }
 
+    if (!ibus_engine_service_authorized_method (service, connection))
+        return FALSE;
+
     if (g_strcmp0 (property_name, "ContentType") == 0) {
         guint purpose = 0;
         guint hints = 0;
diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c
index 468aa324..33949fa1 100644
--- a/src/ibuspanelservice.c
+++ b/src/ibuspanelservice.c
@@ -3,7 +3,7 @@
 /* ibus - The Input Bus
  * Copyright (c) 2009-2014 Google Inc. All rights reserved.
  * Copyright (C) 2010-2014 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -936,6 +936,15 @@ _g_object_unref_if_floating (gpointer instance)
         g_object_unref (instance);
 }
 
+static gboolean
+ibus_panel_service_service_authorized_method (IBusService     *service,
+                                              GDBusConnection *connection)
+{
+    if (ibus_service_get_connection (service) == connection)
+        return TRUE;
+    return FALSE;
+}
+
 static void
 ibus_panel_service_service_method_call (IBusService           *service,
                                         GDBusConnection       *connection,
@@ -961,6 +970,9 @@ ibus_panel_service_service_method_call (IBusService           *service,
         return;
     }
 
+    if (!ibus_panel_service_service_authorized_method (service, connection))
+        return;
+
     if (g_strcmp0 (method_name, "UpdatePreeditText") == 0) {
         GVariant *variant = NULL;
         guint cursor = 0;
-- 
2.14.3

From e17c99859d06ab75326730e45072e1061f7d87c7 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 29 Jan 2018 16:50:07 +0900
Subject: [PATCH] Implement Unicode choice on Emojier

BUG=https://github.com/ibus/ibus/issues/1922

Review URL: https://codereview.appspot.com/340190043
---
 configure.ac              |   41 +-
 po/POTFILES.in            |    1 +
 src/Makefile.am           |   55 ++-
 src/emoji-parser.c        |    3 +-
 src/ibus.h                |    4 +-
 src/ibusunicode.c         | 1069 +++++++++++++++++++++++++++++++++++++++++
 src/ibusunicode.h         |  299 ++++++++++++
 src/ibusunicodegen.h      | 1151 +++++++++++++++++++++++++++++++++++++++++++++
 src/unicode-parser.c      |  502 ++++++++++++++++++++
 ui/gtk3/emojier.vala      |  550 +++++++++++++++++++---
 ui/gtk3/emojierapp.vala   |    2 +
 ui/gtk3/ibusemojidialog.h |    9 +-
 ui/gtk3/panel.vala        |    3 +-
 13 files changed, 3613 insertions(+), 76 deletions(-)
 create mode 100644 src/ibusunicode.c
 create mode 100644 src/ibusunicode.h
 create mode 100644 src/ibusunicodegen.h
 create mode 100644 src/unicode-parser.c

diff --git a/configure.ac b/configure.ac
index 4789328e..bd41069b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,8 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
-# Copyright (c) 2007-2017 Red Hat, Inc.
+# Copyright (c) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-2018 Red Hat, Inc.
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -653,6 +653,41 @@ https://github.com/fujiwarat/cldr-emoji-annotation)
     enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
 fi
 
+# --disable-unicode-dict option.
+AC_ARG_ENABLE(unicode-dict,
+    AS_HELP_STRING([--disable-unicode-dict],
+                   [Do not build Unicode dict files]),
+    [enable_unicode_dict=$enableval],
+    [enable_unicode_dict=yes]
+)
+AM_CONDITIONAL([ENABLE_UNICODE_DICT], [test x"$enable_unicode_dict" = x"yes"])
+
+AC_ARG_WITH(ucd-dir,
+    AS_HELP_STRING([--with-ucd-dir[=DIR]],
+        [Set the directory of UCD (Unicode Character Database) files.
+         (default: "/usr/share/unicode/ucd")]),
+    UCD_DIR=$with_emoji_annotation_dir,
+    UCD_DIR="/usr/share/unicode/ucd"
+)
+AC_SUBST(UCD_DIR)
+
+if test x"$enable_unicode_dict" = x"yes"; then
+    if test ! -f $UCD_DIR/NamesList.txt ; then
+        AC_MSG_ERROR(Not found $UCD_DIR/NamesList.txt. You can get \
+the UCD files from https://www.unicode.org/Public/UNIDATA/)
+    elif test ! -f $UCD_DIR/Blocks.txt ; then
+        AC_MSG_ERROR(Not found $UCD_DIR/Blocks.txt. You can get \
+the UCD files from https://www.unicode.org/Public/UNIDATA/)
+    else
+        # POSIX SHELL has no ${FOO:0:1}
+        head=`echo "$UCD_DIR" | cut -c1`;
+        if test $head != "/" ; then
+            UCD_DIR=`realpath "$UCD_DIR"`
+        fi
+    fi
+    enable_unicode_dict="yes (enabled, use --disable-unicode-dict to disable)"
+fi
+
 # Check iso-codes.
 PKG_CHECK_MODULES(ISOCODES, [
     iso-codes
@@ -743,6 +778,8 @@ Build options:
   Enable Emoji dict             $enable_emoji_dict
   Unicode Emoji directory       $UNICODE_EMOJI_DIR
   CLDR annotation directory     $EMOJI_ANNOTATION_DIR
+  Enable Unicode dict           $enable_unicode_dict
+  UCD directory                 $UCD_DIR
   Run test cases                $enable_tests
 ])
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 00f7c7f6..58d1e39d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -51,6 +51,7 @@ src/ibuspanelservice.c
 src/ibusproxy.c
 src/ibusregistry.c
 src/ibusservice.c
+src/ibusunicodegen.h
 src/ibusutil.c
 src/keyname-table.h
 tools/main.vala
diff --git a/src/Makefile.am b/src/Makefile.am
index 303250f5..1ba418d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -41,6 +41,7 @@ INTROSPECTION_COMPILER_ARGS = \
     $(NULL)
 INTROSPECTION_GIRS =
 CLEANFILES =
+noinst_PROGRAMS =
 
 # C preprocessor flags
 AM_CPPFLAGS =                                           \
@@ -100,6 +101,7 @@ ibus_sources =              \
     ibusservice.c           \
     ibusshare.c             \
     ibustext.c              \
+    ibusunicode.c           \
     ibusutil.c              \
     ibusxml.c               \
     $(NULL)
@@ -151,6 +153,7 @@ ibus_headers =              \
     ibusshare.h             \
     ibustext.h              \
     ibustypes.h             \
+    ibusunicode.h           \
     ibusutil.h              \
     ibusxml.h               \
     $(NULL)
@@ -169,6 +172,7 @@ ibus_private_headers =          \
     ibusemojigen.h              \
     ibusenginesimpleprivate.h   \
     ibusinternal.h              \
+    ibusunicodegen.h            \
     keyname-table.h             \
     $(NULL)
 noinst_HEADERS =            \
@@ -241,7 +245,7 @@ dictdir = $(pkgdatadir)/dicts
 dict_DATA = dicts/emoji-en.dict
 LANG_FILES = $(basename $(notdir $(wildcard $(EMOJI_ANNOTATION_DIR)/*.xml)))
 
-noinst_PROGRAMS = emoji-parser
+noinst_PROGRAMS += emoji-parser
 
 dicts/emoji-en.dict: emoji-parser
 	$(AM_V_at)if test x"$(LANG_FILES)" = x ; then \
@@ -325,12 +329,60 @@ clean-local:
 	$(NULL)
 endif
 
+if ENABLE_UNICODE_DICT
+unicodedir = $(pkgdatadir)/dicts
+unicode_DATA = dicts/unicode-names.dict dicts/unicode-blocks.dict
+noinst_PROGRAMS += unicode-parser
+
+dicts/unicode-names.dict: unicode-parser
+	$(AM_V_at)input_file="$(UCD_DIR)/NamesList.txt"; \
+	if test ! -f "$$input_file" ; then \
+	    echo "WARNING: Not found $$input_file" 1>&2; \
+	else \
+	    $(builddir)/unicode-parser \
+	        --input-names-list $$input_file \
+	        --output-names-list $@; \
+	    echo "Generated $@"; \
+	fi;
+
+dicts/unicode-blocks.dict: unicode-parser
+	$(AM_V_at)input_file="$(UCD_DIR)/Blocks.txt"; \
+	if test ! -f "$$input_file" ; then \
+	    echo "WARNING: Not found $$input_file" 1>&2; \
+	else \
+	    $(builddir)/unicode-parser \
+	        --input-blocks $$input_file \
+	        --output-blocks-trans ibusunicodegen.h \
+	        --output-blocks $@; \
+	    echo "Generated $@"; \
+	fi;
+
+ibusunicodegen.h: dicts/unicode-blocks.dict
+	$(NULL)
+
+unicode_parser_SOURCES =        \
+    unicode-parser.c            \
+    $(NULL)
+unicode_parser_CFLAGS =         \
+    $(GLIB2_CFLAGS)             \
+    $(NULL)
+unicode_parser_LDADD =          \
+    $(GLIB2_LIBS)               \
+    $(libibus)                  \
+    $(NULL)
+
+clean-local:
+	-rm -rf dicts
+	$(NULL)
+endif
+
 EXTRA_DIST =                    \
     emoji-parser.c              \
     ibusversion.h.in            \
     ibusmarshalers.list         \
     ibusenumtypes.h.template    \
     ibusenumtypes.c.template    \
+    unicode-parser.c            \
     $(NULL)
 
 CLEANFILES +=                   \
@@ -341,6 +393,7 @@ CLEANFILES +=                   \
 
 DISTCLEANFILES =                \
     ibusemojigen.h              \
+    ibusunicodegen.h            \
     ibusversion.h               \
     $(NULL)
 
diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index fe3e4ef8..0f7c8cfb 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -1,7 +1,7 @@
 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
- * Copyright (C) 2016-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2016-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2016 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -1126,6 +1126,7 @@ category_file_save (const gchar *filename,
     if (!g_file_get_contents (__FILE__, &content, &length, &error)) {
         g_warning ("Failed to load %s: %s", __FILE__, error->message);
         g_clear_pointer (&error, g_error_free);
+        return;
     }
     buff = g_string_new (NULL);
     p = content;
diff --git a/src/ibus.h b/src/ibus.h
index c6cf58cf..8011729f 100644
--- a/src/ibus.h
+++ b/src/ibus.h
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -57,6 +58,7 @@
 #include <ibusutil.h>
 #include <ibusregistry.h>
 #include <ibusemoji.h>
+#include <ibusunicode.h>
 
 #ifndef IBUS_DISABLE_DEPRECATED
 #include <ibuskeysyms-compat.h>
diff --git a/src/ibusunicode.c b/src/ibusunicode.c
new file mode 100644
index 00000000..8559819d
--- /dev/null
+++ b/src/ibusunicode.c
@@ -0,0 +1,1069 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* bus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include "ibusinternal.h"
+#include "ibuserror.h"
+#include "ibusunicode.h"
+
+#define IBUS_UNICODE_DATA_MAGIC "IBusUnicodeData"
+#define IBUS_UNICODE_BLOCK_MAGIC "IBusUnicodeBlock"
+#define IBUS_UNICODE_DATA_VERSION (1)
+#define IBUS_UNICODE_DESERIALIZE_SIGNALL_STR \
+        "deserialize-unicode"
+
+enum {
+    PROP_0 = 0,
+    PROP_CODE,
+    PROP_NAME,
+    PROP_ALIAS,
+    PROP_BLOCK_NAME,
+    PROP_START,
+    PROP_END
+};
+
+struct _IBusUnicodeDataPrivate {
+    gunichar    code;
+    gchar      *name;
+    gchar      *alias;
+    gchar      *block_name;
+};
+
+struct _IBusUnicodeBlockPrivate {
+    gunichar    start;
+    gunichar    end;
+    gchar      *name;
+};
+
+typedef struct {
+    IBusUnicodeDataLoadAsyncFinish callback;
+    gpointer                       user_data;
+} IBusUnicodeDataLoadData;
+
+#define IBUS_UNICODE_DATA_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+    IBUS_TYPE_UNICODE_DATA, \
+    IBusUnicodeDataPrivate))
+#define IBUS_UNICODE_BLOCK_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+    IBUS_TYPE_UNICODE_BLOCK, \
+    IBusUnicodeBlockPrivate))
+
+/* functions prototype */
+static void      ibus_unicode_data_set_property (IBusUnicodeData      *unicode,
+                                                 guint                 prop_id,
+                                                 const GValue         *value,
+                                                 GParamSpec           *pspec);
+static void      ibus_unicode_data_get_property (IBusUnicodeData      *unicode,
+                                                 guint                 prop_id,
+                                                 GValue               *value,
+                                                 GParamSpec           *pspec);
+static void      ibus_unicode_data_destroy      (IBusUnicodeData      *unicode);
+static gboolean  ibus_unicode_data_serialize    (IBusUnicodeData      *unicode,
+                                                 GVariantBuilder      *builder);
+static gint      ibus_unicode_data_deserialize  (IBusUnicodeData      *unicode,
+                                                 GVariant             *variant);
+static gboolean  ibus_unicode_data_copy         (IBusUnicodeData      *dest,
+                                                const IBusUnicodeData *src);
+static void      ibus_unicode_block_set_property
+                                                (IBusUnicodeBlock     *block,
+                                                 guint                 prop_id,
+                                                 const GValue         *value,
+                                                 GParamSpec           *pspec);
+static void      ibus_unicode_block_get_property
+                                                (IBusUnicodeBlock     *block,
+                                                 guint                 prop_id,
+                                                 GValue               *value,
+                                                 GParamSpec           *pspec);
+static void      ibus_unicode_block_destroy     (IBusUnicodeBlock     *block);
+static gboolean  ibus_unicode_block_serialize   (IBusUnicodeBlock     *block,
+                                                 GVariantBuilder      *builder);
+static gint      ibus_unicode_block_deserialize (IBusUnicodeBlock     *block,
+                                                 GVariant             *variant);
+static gboolean  ibus_unicode_block_copy        (IBusUnicodeBlock     *dest,
+                                                const IBusUnicodeBlock *src);
+
+G_DEFINE_TYPE (IBusUnicodeData, ibus_unicode_data, IBUS_TYPE_SERIALIZABLE)
+G_DEFINE_TYPE (IBusUnicodeBlock, ibus_unicode_block, IBUS_TYPE_SERIALIZABLE)
+
+static void
+ibus_unicode_data_class_init (IBusUnicodeDataClass *class)
+{
+    IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
+    GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+    IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
+
+    object_class->destroy = (IBusObjectDestroyFunc) ibus_unicode_data_destroy;
+    gobject_class->set_property =
+            (GObjectSetPropertyFunc) ibus_unicode_data_set_property;
+    gobject_class->get_property =
+            (GObjectGetPropertyFunc) ibus_unicode_data_get_property;
+    serializable_class->serialize   =
+            (IBusSerializableSerializeFunc) ibus_unicode_data_serialize;
+    serializable_class->deserialize =
+            (IBusSerializableDeserializeFunc) ibus_unicode_data_deserialize;
+    serializable_class->copy        =
+            (IBusSerializableCopyFunc) ibus_unicode_data_copy;
+
+    g_type_class_add_private (class, sizeof (IBusUnicodeDataPrivate));
+
+    /* install properties */
+    /**
+     * IBusUnicodeData:code:
+     *
+     * The Uniode code point
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_CODE,
+                    g_param_spec_unichar ("code",
+                        "code point",
+                        "The Unicode code point",
+                        0,
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+
+    /**
+     * IBusUnicodeData:name:
+     *
+     * The Uniode name
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_NAME,
+                    g_param_spec_string ("name",
+                        "name",
+                        "The Unicode name",
+                        "",
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+    /**
+     * IBusUnicodeData:alias:
+     *
+     * The Uniode alias name
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_ALIAS,
+                    g_param_spec_string ("alias",
+                        "alias name",
+                        "The Unicode alias name",
+                        "",
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+    /**
+     * IBusUnicodeData:block-name:
+     *
+     * The Uniode block name
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_BLOCK_NAME,
+                    g_param_spec_string ("block-name",
+                        "block name",
+                        "The Unicode block name",
+                        "",
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+ibus_unicode_data_init (IBusUnicodeData *unicode)
+{
+    unicode->priv = IBUS_UNICODE_DATA_GET_PRIVATE (unicode);
+}
+
+static void
+ibus_unicode_data_destroy (IBusUnicodeData *unicode)
+{
+    g_clear_pointer (&unicode->priv->name, g_free);
+    g_clear_pointer (&unicode->priv->alias, g_free);
+    g_clear_pointer (&unicode->priv->block_name, g_free);
+
+    IBUS_OBJECT_CLASS (ibus_unicode_data_parent_class)->
+            destroy (IBUS_OBJECT (unicode));
+}
+
+static void
+ibus_unicode_data_set_property (IBusUnicodeData *unicode,
+                                guint            prop_id,
+                                const GValue    *value,
+                                GParamSpec      *pspec)
+{
+    switch (prop_id) {
+    case PROP_CODE:
+        g_assert (unicode->priv->code == 0);
+        unicode->priv->code = g_value_get_uint (value);
+        break;
+    case PROP_NAME:
+        g_assert (unicode->priv->name == NULL);
+        unicode->priv->name = g_value_dup_string (value);
+        break;
+    case PROP_ALIAS:
+        g_assert (unicode->priv->alias == NULL);
+        unicode->priv->alias = g_value_dup_string (value);
+        break;
+    case PROP_BLOCK_NAME:
+        g_free (unicode->priv->block_name);
+        unicode->priv->block_name = g_value_dup_string (value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (unicode, prop_id, pspec);
+    }
+}
+
+static void
+ibus_unicode_data_get_property (IBusUnicodeData *unicode,
+                                guint            prop_id,
+                                GValue          *value,
+                                GParamSpec      *pspec)
+{
+    switch (prop_id) {
+    case PROP_CODE:
+        g_value_set_uint (value, ibus_unicode_data_get_code (unicode));
+        break;
+    case PROP_NAME:
+        g_value_set_string (value, ibus_unicode_data_get_name (unicode));
+        break;
+    case PROP_ALIAS:
+        g_value_set_string (value, ibus_unicode_data_get_alias (unicode));
+        break;
+    case PROP_BLOCK_NAME:
+        g_value_set_string (value, ibus_unicode_data_get_block_name (unicode));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (unicode, prop_id, pspec);
+    }
+}
+
+static gboolean
+ibus_unicode_data_serialize (IBusUnicodeData   *unicode,
+                             GVariantBuilder   *builder)
+{
+    gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_data_parent_class)->
+            serialize ((IBusSerializable *)unicode, builder);
+    g_return_val_if_fail (retval, FALSE);
+
+#define NOTNULL(s) ((s) != NULL ? (s) : "")
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_builder_add (builder, "u", unicode->priv->code);
+    g_variant_builder_add (builder, "s", NOTNULL (unicode->priv->name));
+    g_variant_builder_add (builder, "s", NOTNULL (unicode->priv->alias));
+    /* Use IBusUnicodeBlock for memory usage.
+    g_variant_builder_add (builder, "s", NOTNULL (unicode->priv->block_name));
+     */
+#undef NOTNULL
+    return TRUE;
+}
+
+static gint
+ibus_unicode_data_deserialize (IBusUnicodeData *unicode,
+                               GVariant        *variant)
+{
+    gint retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_data_parent_class)->
+            deserialize ((IBusSerializable *)unicode, variant);
+    g_return_val_if_fail (retval, 0);
+
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_get_child (variant, retval++, "u", &unicode->priv->code);
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &unicode->priv->name);
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &unicode->priv->alias);
+    /* Use IBusUnicodeBlock for memory usage.
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &unicode->priv->block_name);
+     */
+    return retval;
+}
+
+static gboolean
+ibus_unicode_data_copy (IBusUnicodeData       *dest,
+                        const IBusUnicodeData *src)
+{
+    gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_data_parent_class)->
+            copy ((IBusSerializable *)dest,
+                  (IBusSerializable *)src);
+    g_return_val_if_fail (retval, FALSE);
+
+    dest->priv->code             = src->priv->code;
+    dest->priv->name             = g_strdup (src->priv->name);
+    dest->priv->alias            = g_strdup (src->priv->alias);
+    dest->priv->block_name       = g_strdup (src->priv->block_name);
+    return TRUE;
+}
+
+IBusUnicodeData *
+ibus_unicode_data_new (const gchar *first_property_name, ...)
+{
+    va_list var_args;
+    IBusUnicodeData *unicode;
+
+    g_assert (first_property_name != NULL);
+    va_start (var_args, first_property_name);
+    unicode = (IBusUnicodeData *) g_object_new_valist (IBUS_TYPE_UNICODE_DATA,
+                                                       first_property_name,
+                                                       var_args);
+    va_end (var_args);
+    /* code is required. Other properties are set in class_init by default. */
+    g_assert (unicode->priv->name != NULL);
+    g_assert (unicode->priv->alias != NULL);
+    g_assert (unicode->priv->block_name != NULL);
+    return unicode;
+}
+
+gunichar
+ibus_unicode_data_get_code (IBusUnicodeData *unicode)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_DATA (unicode), G_MAXUINT32);
+
+    return unicode->priv->code;
+}
+
+const gchar *
+ibus_unicode_data_get_name (IBusUnicodeData *unicode)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_DATA (unicode), "");
+
+    return unicode->priv->name;
+}
+
+const gchar *
+ibus_unicode_data_get_alias (IBusUnicodeData *unicode)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_DATA (unicode), "");
+
+    return unicode->priv->alias;
+}
+
+const gchar *
+ibus_unicode_data_get_block_name (IBusUnicodeData *unicode)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_DATA (unicode), "");
+
+    return unicode->priv->block_name;
+}
+
+void
+ibus_unicode_data_set_block_name (IBusUnicodeData *unicode,
+                                  const gchar     *block_name)
+{
+    g_return_if_fail (IBUS_IS_UNICODE_DATA (unicode));
+
+    g_free (unicode->priv->block_name);
+    unicode->priv->block_name = g_strdup (block_name);
+}
+
+static void
+variant_foreach_add_unicode (IBusUnicodeData *unicode,
+                             GVariantBuilder *builder)
+{
+    g_variant_builder_add (
+            builder, "v",
+            ibus_serializable_serialize (IBUS_SERIALIZABLE (unicode)));
+}
+
+static GVariant *
+ibus_unicode_data_list_serialize (GSList *list)
+{
+    GVariantBuilder builder;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+    g_slist_foreach (list,  (GFunc) variant_foreach_add_unicode, &builder);
+    return g_variant_builder_end (&builder);
+}
+
+static GSList *
+ibus_unicode_data_list_deserialize (GVariant *variant,
+                                    GObject  *source_object)
+{
+    GSList *list = NULL;
+    GVariantIter iter;
+    GVariant *unicode_variant = NULL;
+    gsize i, size;
+    gboolean has_signal = FALSE;
+
+    if (G_IS_OBJECT (source_object)) {
+        has_signal = g_signal_lookup (
+                IBUS_UNICODE_DESERIALIZE_SIGNALL_STR,
+                G_OBJECT_TYPE (source_object));
+        if (!has_signal) {
+            const gchar type_name = g_type_name (source_object);
+            g_warning ("GObject %s does not have the signal \"%s\"",
+                       type_name ? type_name : "(null)",
+                       IBUS_UNICODE_DESERIALIZE_SIGNALL_STR);
+        }
+    }
+    g_variant_iter_init (&iter, variant);
+    size = g_variant_iter_n_children (&iter);
+    i = 0;
+    while (g_variant_iter_loop (&iter, "v", &unicode_variant)) {
+        IBusUnicodeData *data =
+                IBUS_UNICODE_DATA (ibus_serializable_deserialize (
+                        unicode_variant));
+        list = g_slist_append (list, data);
+        g_clear_pointer (&unicode_variant, g_variant_unref);
+        if (has_signal && (i == 0 || ((i + 1) % 100) == 0)) {
+            g_signal_emit_by_name (source_object,
+                                   IBUS_UNICODE_DESERIALIZE_SIGNALL_STR,
+                                   i + 1, size);
+        }
+        i++;
+    }
+    if (has_signal && (i != 1 && (i % 100) != 0)) {
+        g_signal_emit_by_name (source_object,
+                               IBUS_UNICODE_DESERIALIZE_SIGNALL_STR,
+                               i, size);
+    }
+
+    return list;
+}
+
+void
+ibus_unicode_data_save (const gchar *path,
+                        GSList      *list)
+{
+    GVariant *variant;
+    const gchar *header = IBUS_UNICODE_DATA_MAGIC;
+    const guint16 version = IBUS_UNICODE_DATA_VERSION;
+    const gchar *contents;
+    gsize length;
+    gchar *dir;
+    GStatBuf buf = { 0, };
+    GError *error = NULL;
+
+    g_return_if_fail (path != NULL);
+    g_return_if_fail (list != NULL);
+    if (list->data == NULL) {
+        g_warning ("Failed to save IBus Unicode data: Need a list data.");
+        return;
+    }
+
+    variant = g_variant_new ("(sqv)",
+                             header,
+                             version,
+                             ibus_unicode_data_list_serialize (list));
+
+    contents =  g_variant_get_data (variant);
+    length =  g_variant_get_size (variant);
+
+    dir = g_path_get_dirname (path);
+    if (g_strcmp0 (dir, ".") != 0 && g_stat (dir, &buf) != 0) {
+        g_mkdir_with_parents (dir, 0777);
+    }
+    g_free (dir);
+    if (!g_file_set_contents (path, contents, length, &error)) {
+        g_warning ("Failed to save Unicode dict %s: %s", path, error->message);
+        g_error_free (error);
+    }
+
+    g_variant_unref (variant);
+}
+
+static GSList *
+ibus_unicode_data_load_with_error (const gchar *path,
+                                   GObject     *source_object,
+                                   GError     **error)
+{
+    gchar *contents = NULL;
+    gsize length = 0;
+    GVariant *variant_table = NULL;
+    GVariant *variant = NULL;
+    const gchar *header = NULL;
+    guint16 version = 0;
+    GSList *retval = NULL;
+
+    if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "Unicode dict does not exist: %s", path);
+        goto out_load_cache;
+    }
+
+    if (!g_file_get_contents (path, &contents, &length, error)) {
+        goto out_load_cache;
+    }
+
+    variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
+                                             contents,
+                                             length,
+                                             FALSE,
+                                             NULL,
+                                             NULL);
+
+    if (variant_table == NULL) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "cache table is broken.");
+        goto out_load_cache;
+    }
+
+    g_variant_get (variant_table, "(&sq)", &header, &version);
+
+    if (g_strcmp0 (header, IBUS_UNICODE_DATA_MAGIC) != 0) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "cache is not IBusUnicodeData.");
+        goto out_load_cache;
+    }
+
+    if (version > IBUS_UNICODE_DATA_VERSION) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "cache version is different: %u != %u",
+                     version, IBUS_UNICODE_DATA_VERSION);
+        goto out_load_cache;
+    }
+
+    version = 0;
+    header = NULL;
+    g_variant_unref (variant_table);
+
+    variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
+                                             contents,
+                                             length,
+                                             FALSE,
+                                             NULL,
+                                             NULL);
+
+    if (variant_table == NULL) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "cache table is broken.");
+        goto out_load_cache;
+    }
+
+    g_variant_get (variant_table, "(&sqv)",
+                   NULL,
+                   NULL,
+                   &variant);
+
+    if (variant == NULL) {
+        g_set_error (error,
+                     IBUS_ERROR,
+                     IBUS_ERROR_FAILED,
+                     "cache dict is broken.");
+        goto out_load_cache;
+    }
+
+    retval = ibus_unicode_data_list_deserialize (variant, source_object);
+
+out_load_cache:
+    if (variant)
+        g_variant_unref (variant);
+    if (variant_table)
+        g_variant_unref (variant_table);
+    g_free (contents);
+
+    return retval;
+}
+
+GSList *
+ibus_unicode_data_load (const gchar *path,
+                        GObject     *source_object)
+{
+    GError *error = NULL;
+    GSList *retval = ibus_unicode_data_load_with_error (path,
+                                                        source_object,
+                                                        &error);
+
+    if (retval == NULL) {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+    }
+
+    return retval;
+}
+
+static void
+ibus_unicode_data_load_async_thread (GTask        *task,
+                                     gpointer      source_object,
+                                     gpointer      task_data,
+                                     GCancellable *cancellable)
+{
+    GSList *retval;
+    gchar *path = (gchar *)task_data;
+    GError *error = NULL;
+
+    g_assert (path != NULL);
+
+    retval = ibus_unicode_data_load_with_error (path, source_object, &error);
+    g_free (path);
+    if (retval == NULL)
+        g_task_return_error (task, error);
+    else
+        g_task_return_pointer (task, retval, NULL);
+    g_object_unref (task);
+}
+
+static void
+ibus_unicode_data_load_async_done (GObject *source_object,
+                                   GAsyncResult *res,
+                                   gpointer user_data)
+{
+    IBusUnicodeDataLoadData *data = (IBusUnicodeDataLoadData*)user_data;
+    GSList *list;
+    GError *error = NULL;
+    g_assert (data != NULL);
+    list = g_task_propagate_pointer (G_TASK (res), &error);
+    if (error) {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+        data->callback (NULL, data->user_data);
+    } else {
+        data->callback (list, data->user_data);
+    }
+    g_slice_free (IBusUnicodeDataLoadData, data);
+}
+
+void
+ibus_unicode_data_load_async (const gchar        *path,
+                              GObject            *source_object,
+                              GCancellable       *cancellable,
+                              IBusUnicodeDataLoadAsyncFinish
+                                                  callback,
+                              gpointer            user_data)
+{
+    GTask *task;
+    IBusUnicodeDataLoadData *data;
+
+    g_return_if_fail (path != NULL);
+
+    data = g_slice_new0 (IBusUnicodeDataLoadData);
+    data->callback = callback;
+    data->user_data = user_data;
+    task = g_task_new (source_object,
+                       cancellable,
+                       ibus_unicode_data_load_async_done,
+                       data);
+    g_task_set_source_tag (task, ibus_unicode_data_load_async);
+    g_task_set_task_data (task, g_strdup (path), NULL);
+    g_task_run_in_thread (task, ibus_unicode_data_load_async_thread);
+}
+
+static void
+ibus_unicode_block_class_init (IBusUnicodeBlockClass *class)
+{
+    IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
+    GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+    IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
+
+    object_class->destroy = (IBusObjectDestroyFunc) ibus_unicode_data_destroy;
+    gobject_class->set_property =
+            (GObjectSetPropertyFunc) ibus_unicode_block_set_property;
+    gobject_class->get_property =
+            (GObjectGetPropertyFunc) ibus_unicode_block_get_property;
+    serializable_class->serialize   =
+            (IBusSerializableSerializeFunc) ibus_unicode_block_serialize;
+    serializable_class->deserialize =
+            (IBusSerializableDeserializeFunc) ibus_unicode_block_deserialize;
+    serializable_class->copy        =
+            (IBusSerializableCopyFunc) ibus_unicode_block_copy;
+
+    g_type_class_add_private (class, sizeof (IBusUnicodeBlockPrivate));
+
+    /* install properties */
+    /**
+     * IBusUnicodeBlock:start:
+     *
+     * The Uniode start code point
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_START,
+                    /* Cannot use g_param_spec_unichar() for the Unicode
+                     * boundary values because the function checks
+                     * if the value is a valid Unicode besides MAXUINT.
+                     */
+                    g_param_spec_uint ("start",
+                        "start code point",
+                        "The Unicode start code point",
+                        0,
+                        G_MAXUINT,
+                        0,
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+
+    /**
+     * IBusUnicodeBlock:end:
+     *
+     * The Uniode end code point
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_END,
+                    /* Cannot use g_param_spec_unichar() for the Unicode
+                     * boundary values because the function checks
+                     * if the value is a valid Unicode besides MAXUINT.
+                     */
+                    g_param_spec_uint ("end",
+                        "end code point",
+                        "The Unicode end code point",
+                        0,
+                        G_MAXUINT,
+                        0,
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+
+    /**
+     * IBusUnicodeBlock:name:
+     *
+     * The Uniode block name
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_NAME,
+                    g_param_spec_string ("name",
+                        "name",
+                        "The Unicode name",
+                        "",
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+ibus_unicode_block_init (IBusUnicodeBlock *block)
+{
+    block->priv = IBUS_UNICODE_BLOCK_GET_PRIVATE (block);
+}
+
+static void
+ibus_unicode_block_destroy (IBusUnicodeBlock *block)
+{
+    g_clear_pointer (&block->priv->name, g_free);
+
+    IBUS_OBJECT_CLASS (ibus_unicode_data_parent_class)->
+            destroy (IBUS_OBJECT (block));
+}
+
+static void
+ibus_unicode_block_set_property (IBusUnicodeBlock *block,
+                                 guint             prop_id,
+                                 const GValue     *value,
+                                 GParamSpec       *pspec)
+{
+    switch (prop_id) {
+    case PROP_START:
+        g_assert (block->priv->start == 0);
+        block->priv->start = g_value_get_uint (value);
+        break;
+    case PROP_END:
+        g_assert (block->priv->end == 0);
+        block->priv->end = g_value_get_uint (value);
+        break;
+    case PROP_NAME:
+        g_assert (block->priv->name == NULL);
+        block->priv->name = g_value_dup_string (value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (block, prop_id, pspec);
+    }
+}
+
+static void
+ibus_unicode_block_get_property (IBusUnicodeBlock *block,
+                                 guint             prop_id,
+                                 GValue           *value,
+                                 GParamSpec       *pspec)
+{
+    switch (prop_id) {
+    case PROP_START:
+        g_value_set_uint (value, ibus_unicode_block_get_start (block));
+        break;
+    case PROP_END:
+        g_value_set_uint (value, ibus_unicode_block_get_end (block));
+        break;
+    case PROP_NAME:
+        g_value_set_string (value, ibus_unicode_block_get_name (block));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (block, prop_id, pspec);
+    }
+}
+
+static gboolean
+ibus_unicode_block_serialize (IBusUnicodeBlock *block,
+                              GVariantBuilder  *builder)
+{
+    gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_block_parent_class)->
+            serialize ((IBusSerializable *)block, builder);
+    g_return_val_if_fail (retval, FALSE);
+
+#define NOTNULL(s) ((s) != NULL ? (s) : "")
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_builder_add (builder, "u", block->priv->start);
+    g_variant_builder_add (builder, "u", block->priv->end);
+    g_variant_builder_add (builder, "s", NOTNULL (block->priv->name));
+#undef NOTNULL
+    return TRUE;
+}
+
+static gint
+ibus_unicode_block_deserialize (IBusUnicodeBlock *block,
+                                GVariant        *variant)
+{
+    gint retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_block_parent_class)->
+            deserialize ((IBusSerializable *)block, variant);
+    g_return_val_if_fail (retval, 0);
+
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_get_child (variant, retval++, "u", &block->priv->start);
+    g_variant_get_child (variant, retval++, "u", &block->priv->end);
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &block->priv->name);
+    return retval;
+}
+
+static gboolean
+ibus_unicode_block_copy (IBusUnicodeBlock       *dest,
+                         const IBusUnicodeBlock *src)
+{
+    gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_unicode_block_parent_class)->
+            copy ((IBusSerializable *)dest,
+                  (IBusSerializable *)src);
+    g_return_val_if_fail (retval, FALSE);
+
+    dest->priv->start            = src->priv->start;
+    dest->priv->end              = src->priv->end;
+    dest->priv->name             = g_strdup (src->priv->name);
+    return TRUE;
+}
+
+IBusUnicodeBlock *
+ibus_unicode_block_new (const gchar *first_property_name, ...)
+{
+    va_list var_args;
+    IBusUnicodeBlock *block;
+
+    g_assert (first_property_name != NULL);
+    va_start (var_args, first_property_name);
+    block = (IBusUnicodeBlock *) g_object_new_valist (IBUS_TYPE_UNICODE_BLOCK,
+                                                     first_property_name,
+                                                     var_args);
+    va_end (var_args);
+    /* end is required. Other properties are set in class_init by default. */
+    g_assert (block->priv->start != block->priv->end);
+    g_assert (block->priv->name != NULL);
+    return block;
+}
+
+gunichar
+ibus_unicode_block_get_start (IBusUnicodeBlock *block)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_BLOCK (block), G_MAXUINT32);
+
+    return block->priv->start;
+}
+
+gunichar
+ibus_unicode_block_get_end (IBusUnicodeBlock *block)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_BLOCK (block), G_MAXUINT32);
+
+    return block->priv->end;
+}
+
+const gchar *
+ibus_unicode_block_get_name (IBusUnicodeBlock *block)
+{
+    g_return_val_if_fail (IBUS_IS_UNICODE_BLOCK (block), "");
+
+    return block->priv->name;
+}
+
+static void
+variant_foreach_add_block (IBusUnicodeBlock *block,
+                           GVariantBuilder *builder)
+{
+    g_variant_builder_add (
+            builder, "v",
+            ibus_serializable_serialize (IBUS_SERIALIZABLE (block)));
+}
+
+static GVariant *
+ibus_unicode_block_list_serialize (GSList *list)
+{
+    GVariantBuilder builder;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+    g_slist_foreach (list,  (GFunc) variant_foreach_add_block, &builder);
+    return g_variant_builder_end (&builder);
+}
+
+static GSList *
+ibus_unicode_block_list_deserialize (GVariant *variant)
+{
+    GSList *list = NULL;
+    GVariantIter iter;
+    GVariant *unicode_variant = NULL;
+
+    g_variant_iter_init (&iter, variant);
+    while (g_variant_iter_loop (&iter, "v", &unicode_variant)) {
+        IBusUnicodeBlock *data =
+                IBUS_UNICODE_BLOCK (ibus_serializable_deserialize (
+                        unicode_variant));
+        list = g_slist_append (list, data);
+        g_clear_pointer (&unicode_variant, g_variant_unref);
+    }
+
+    return list;
+}
+
+void
+ibus_unicode_block_save (const gchar *path,
+                         GSList      *list)
+{
+    GVariant *variant;
+    const gchar *header = IBUS_UNICODE_BLOCK_MAGIC;
+    const guint16 version = IBUS_UNICODE_DATA_VERSION;
+    const gchar *contents;
+    gsize length;
+    gchar *dir;
+    GStatBuf buf = { 0, };
+    GError *error = NULL;
+
+    g_return_if_fail (path != NULL);
+    g_return_if_fail (list != NULL);
+    if (list->data == NULL) {
+        g_warning ("Failed to save IBus Unicode block: Need a list data.");
+        return;
+    }
+
+    variant = g_variant_new ("(sqv)",
+                             header,
+                             version,
+                             ibus_unicode_block_list_serialize (list));
+
+    contents =  g_variant_get_data (variant);
+    length =  g_variant_get_size (variant);
+
+    dir = g_path_get_dirname (path);
+    if (g_strcmp0 (dir, ".") != 0 && g_stat (dir, &buf) != 0) {
+        g_mkdir_with_parents (dir, 0777);
+    }
+    g_free (dir);
+    if (!g_file_set_contents (path, contents, length, &error)) {
+        g_warning ("Failed to save Unicode dict %s: %s", path, error->message);
+        g_error_free (error);
+    }
+
+    g_variant_unref (variant);
+}
+
+GSList *
+ibus_unicode_block_load (const gchar *path)
+{
+    gchar *contents = NULL;
+    gsize length = 0;
+    GError *error = NULL;
+    GVariant *variant_table = NULL;
+    GVariant *variant = NULL;
+    const gchar *header = NULL;
+    guint16 version = 0;
+    GSList *retval = NULL;
+
+    if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+        g_warning ("Unicode dict does not exist: %s", path);
+        goto out_load_cache;
+    }
+
+    if (!g_file_get_contents (path, &contents, &length, &error)) {
+        g_warning ("Failed to get dict content %s: %s", path, error->message);
+        g_error_free (error);
+        goto out_load_cache;
+    }
+
+    variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
+                                             contents,
+                                             length,
+                                             FALSE,
+                                             NULL,
+                                             NULL);
+
+    if (variant_table == NULL) {
+        g_warning ("cache table is broken.");
+        goto out_load_cache;
+    }
+
+    g_variant_get (variant_table, "(&sq)", &header, &version);
+
+    if (g_strcmp0 (header, IBUS_UNICODE_BLOCK_MAGIC) != 0) {
+        g_warning ("cache is not IBusUnicodeBlock.");
+        goto out_load_cache;
+    }
+
+    if (version > IBUS_UNICODE_DATA_VERSION) {
+        g_warning ("cache version is different: %u != %u",
+                   version, IBUS_UNICODE_DATA_VERSION);
+        goto out_load_cache;
+    }
+
+    version = 0;
+    header = NULL;
+    g_variant_unref (variant_table);
+
+    variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
+                                             contents,
+                                             length,
+                                             FALSE,
+                                             NULL,
+                                             NULL);
+
+    if (variant_table == NULL) {
+        g_warning ("cache table is broken.");
+        goto out_load_cache;
+    }
+
+    g_variant_get (variant_table, "(&sqv)",
+                   NULL,
+                   NULL,
+                   &variant);
+
+    if (variant == NULL) {
+        g_warning ("cache dict is broken.");
+        goto out_load_cache;
+    }
+
+    retval = ibus_unicode_block_list_deserialize (variant);
+
+out_load_cache:
+    if (variant)
+        g_variant_unref (variant);
+    if (variant_table)
+        g_variant_unref (variant_table);
+    g_free (contents);
+
+    return retval;
+}
+
diff --git a/src/ibusunicode.h b/src/ibusunicode.h
new file mode 100644
index 00000000..99de9451
--- /dev/null
+++ b/src/ibusunicode.h
@@ -0,0 +1,299 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* bus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#if !defined (__IBUS_H_INSIDE__) && !defined (IBUS_COMPILATION)
+#error "Only <ibus.h> can be included directly"
+#endif
+
+#ifndef __IBUS_UNICODE_H_
+#define __IBUS_UNICODE_H_
+
+/**
+ * SECTION: ibusunicode
+ * @short_description: unicode utility.
+ * @stability: Unstable
+ *
+ * miscellaneous unicode APIs.
+ */
+
+#include <gio/gio.h>
+#include "ibusserializable.h"
+
+/*
+ * Type macros.
+ */
+/* define GOBJECT macros */
+#define IBUS_TYPE_UNICODE_DATA       (ibus_unicode_data_get_type ())
+#define IBUS_UNICODE_DATA(obj)       (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                                      IBUS_TYPE_UNICODE_DATA, IBusUnicodeData))
+#define IBUS_UNICODE_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                                      IBUS_TYPE_UNICODE_DATA, \
+                                      IBusUnicodeDataClass))
+#define IBUS_IS_UNICODE_DATA(obj)    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+                                      IBUS_TYPE_UNICODE_DATA))
+#define IBUS_TYPE_UNICODE_BLOCK      (ibus_unicode_block_get_type ())
+#define IBUS_UNICODE_BLOCK(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                                      IBUS_TYPE_UNICODE_BLOCK, \
+                                      IBusUnicodeBlock))
+#define IBUS_UNICODE_BLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                                      IBUS_TYPE_UNICODE_BLOCK, \
+                                      IBusUnicodeBlockClass))
+#define IBUS_IS_UNICODE_BLOCK(obj)   (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+                                      IBUS_TYPE_UNICODE_BLOCK))
+
+
+G_BEGIN_DECLS
+
+typedef struct _IBusUnicodeData IBusUnicodeData;
+typedef struct _IBusUnicodeDataPrivate IBusUnicodeDataPrivate;
+typedef struct _IBusUnicodeDataClass IBusUnicodeDataClass;
+typedef struct _IBusUnicodeBlock IBusUnicodeBlock;
+typedef struct _IBusUnicodeBlockPrivate IBusUnicodeBlockPrivate;
+typedef struct _IBusUnicodeBlockClass IBusUnicodeBlockClass;
+
+/**
+ * IBusUnicodeDataLoadAsyncFinish:
+ * @data_list: (transfer full) (element-type IBusUnicodeData):
+ *
+ * This callback can receive the list of #IBusUnicodeData.
+ */
+typedef void (*IBusUnicodeDataLoadAsyncFinish) (GSList  *data_list,
+                                                gpointer user_data);
+
+/**
+ * IBusUnicodeData:
+ *
+ * Unicode data likes code, name, alias, block-name.
+ * You can get extended values with g_object_get_properties.
+ */
+struct _IBusUnicodeData {
+    IBusSerializable parent;
+    /* instance members */
+
+    /*< public >*/
+    /*< private >*/
+    IBusUnicodeDataPrivate *priv;
+};
+
+struct _IBusUnicodeDataClass {
+    IBusSerializableClass parent;
+    /* class members */
+};
+
+struct _IBusUnicodeBlock {
+    IBusSerializable parent;
+    /* instance members */
+
+    /*< public >*/
+    /*< private >*/
+    IBusUnicodeBlockPrivate *priv;
+};
+
+struct _IBusUnicodeBlockClass {
+    IBusSerializableClass parent;
+    /* class members */
+};
+
+GType           ibus_unicode_data_get_type    (void);
+GType           ibus_unicode_block_get_type   (void);
+
+/**
+ * ibus_unicode_data_new:
+ * @first_property_name: Name of the first property.
+ * @...: the NULL-terminated arguments of the properties and values.
+ *
+ * Creates a new #IBusUnicodeData.
+ * code property is required. e.g.
+ * ibus_unicode_data_new ("code", 0x3042, NULL)
+ *
+ * Returns: A newly allocated #IBusUnicodeData.
+ */
+IBusUnicodeData * ibus_unicode_data_new       (const gchar *first_property_name,
+                                               ...);
+
+/**
+ * ibus_unicode_data_get_code:
+ * @unicode: An #IBusUnicodeData
+ *
+ * Gets the code point in #IBusUnicodeData.
+ *
+ * Returns: code property in #IBusUnicodeData
+ */
+gunichar          ibus_unicode_data_get_code  (IBusUnicodeData    *unicode);
+
+/**
+ * ibus_unicode_data_get_name:
+ * @unicode: An #IBusUnicodeData
+ *
+ * Gets the name in #IBusUnicodeData. It should not be freed.
+ *
+ * Returns: name property in #IBusUnicodeData
+ */
+const gchar *     ibus_unicode_data_get_name  (IBusUnicodeData    *unicode);
+
+/**
+ * ibus_unicode_data_get_alias:
+ * @unicode: An #IBusUnicodeData
+ *
+ * Gets the alias in #IBusUnicodeData. It should not be freed.
+ *
+ * Returns: alias property in #IBusUnicodeData
+ */
+const gchar *     ibus_unicode_data_get_alias (IBusUnicodeData    *unicode);
+
+/**
+ * ibus_unicode_data_get_block_name:
+ * @unicode: An #IBusUnicodeData
+ *
+ * Gets the block name in #IBusUnicodeData. It should not be freed.
+ *
+ * Returns: block-name property in #IBusUnicodeData
+ */
+const gchar *     ibus_unicode_data_get_block_name
+                                              (IBusUnicodeData    *unicode);
+
+/**
+ * ibus_unicode_data_set_block_name:
+ * @unicode: An #IBusUnicodeData
+ * @block_name: A block name
+ *
+ * Sets the block name in #IBusUnicodeData.
+ */
+void              ibus_unicode_data_set_block_name
+                                              (IBusUnicodeData    *unicode,
+                                               const gchar        *block_name);
+
+/**
+ * ibus_unicode_data_save:
+ * @path: A path of the saved Unicode data.
+ * @list: (element-type IBusUnicodeData) (transfer none): A list of unicode
+ *  data.
+ *
+ * Save the list of #IBusUnicodeData to the cache file.
+ */
+void              ibus_unicode_data_save      (const gchar        *path,
+                                               GSList             *list);
+
+/**
+ * ibus_unicode_data_load:
+ * @path: A path of the saved dictionary file.
+ * @object: (nullable): If the #GObject has "unicode-deserialize-progress"
+ *    signal, this function will emit (the number of desrialized
+ *    #IBusUnicodeData, * the total number of #IBusUnicodeData) of uint values
+ *    with that signal by 100 times. Otherwise %NULL.
+ *
+ * Returns: (element-type IBusUnicodeData) (transfer container):
+ * An #IBusUnicodeData list loaded from the saved cache file.
+ */
+GSList *          ibus_unicode_data_load      (const gchar        *path,
+                                               GObject            *object);
+
+/**
+ * ibus_unicode_data_load_async:
+ * @path: A path of the saved dictionary file.
+ * @object: (nullable): If the #GObject has "unicode-deserialize-progress"
+ *    signal, this function will emit (the number of desrialized
+ *    #IBusUnicodeData, * the total number of #IBusUnicodeData) of uint values
+ *    with that signal by 100 times. Otherwise %NULL.
+ * @cancellable: cancellable.
+ * @callback: (scope notified): IBusUnicodeDataLoadAsyncFinish.
+ * @user_data: User data.
+ *
+ * IBusUnicodeDataLoadAsyncFinish can receive the list of #IBusUnicodeData.
+ */
+void              ibus_unicode_data_load_async
+                                              (const gchar        *path,
+                                               GObject            *object,
+                                               GCancellable       *cancellable,
+                                               IBusUnicodeDataLoadAsyncFinish
+                                                                   callback,
+                                               gpointer            user_data);
+
+/**
+ * ibus_unicode_block_new:
+ * @first_property_name: Name of the first property.
+ * @...: the NULL-terminated arguments of the properties and values.
+ *
+ * Creates a new #IBusUnicodeBlock.
+ * block property is required. e.g.
+ * ibus_unicode_block_new ("start", 0x0000, "end", "0x007f", "name", "basic",
+ * NULL)
+ *
+ * Returns: A newly allocated #IBusUnicodeBlock.
+ */
+IBusUnicodeBlock *ibus_unicode_block_new      (const gchar *first_property_name,
+                                               ...);
+
+/**
+ * ibus_unicode_block_get_start:
+ * @block: An #IBusUnicodeData
+ *
+ * Gets the start code point in #IBusUnicodeBlock.
+ *
+ * Returns: start property in #IBusUnicodeBlock
+ */
+gunichar          ibus_unicode_block_get_start
+                                              (IBusUnicodeBlock   *block);
+
+/**
+ * ibus_unicode_block_get_end:
+ * @block: An #IBusUnicodeData
+ *
+ * Gets the end code point in #IBusUnicodeBlock.
+ *
+ * Returns: end property in #IBusUnicodeBlock
+ */
+gunichar          ibus_unicode_block_get_end
+                                              (IBusUnicodeBlock   *block);
+
+/**
+ * ibus_unicode_block_get_name:
+ * @block: An #IBusUnicodeBlock
+ *
+ * Gets the name in #IBusUnicodeBlock. It should not be freed.
+ *
+ * Returns: name property in #IBusUnicodeBlock
+ */
+const gchar *     ibus_unicode_block_get_name (IBusUnicodeBlock   *block);
+
+/**
+ * ibus_unicode_block_save:
+ * @path: A path of the saved Unicode block.
+ * @list: (element-type IBusUnicodeBlock) (transfer none): A list of unicode
+ *  block.
+ *
+ * Save the list of #IBusUnicodeBlock to the cache file.
+ */
+void              ibus_unicode_block_save     (const gchar        *path,
+                                               GSList             *list);
+
+/**
+ * ibus_unicode_block_load:
+ * @path: A path of the saved dictionary file.
+ *
+ * Returns: (element-type IBusUnicodeBlock) (transfer container):
+ * An #IBusUnicodeBlock list loaded from the saved cache file.
+ */
+GSList *          ibus_unicode_block_load     (const gchar        *path);
+
+G_END_DECLS
+#endif
diff --git a/src/ibusunicodegen.h b/src/ibusunicodegen.h
new file mode 100644
index 00000000..c613b81b
--- /dev/null
+++ b/src/ibusunicodegen.h
@@ -0,0 +1,1151 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+
+/* This file is generated by unicode-parser.c. */
+include <glib/gi18n.h>
+
+#ifndef __IBUS_UNICODE_GEN_H_
+#define __IBUS_UNICODE_GEN_H_
+const static char *unicode_blocks[] = {
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Basic Latin"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin-1 Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("IPA Extensions"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Spacing Modifier Letters"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Combining Diacritical Marks"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Greek and Coptic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cyrillic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cyrillic Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Armenian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hebrew"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Syriac"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Thaana"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("NKo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Samaritan"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mandaic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Syriac Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Devanagari"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bengali"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Gurmukhi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Gujarati"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Oriya"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tamil"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Telugu"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kannada"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Malayalam"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sinhala"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Thai"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Lao"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tibetan"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Myanmar"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Georgian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hangul Jamo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ethiopic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ethiopic Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cherokee"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Unified Canadian Aboriginal Syllabics"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ogham"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Runic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tagalog"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hanunoo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Buhid"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tagbanwa"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Khmer"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mongolian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Unified Canadian Aboriginal Syllabics Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Limbu"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tai Le"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("New Tai Lue"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Khmer Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Buginese"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tai Tham"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Combining Diacritical Marks Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Balinese"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sundanese"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Batak"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Lepcha"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ol Chiki"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cyrillic Extended-C"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sundanese Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Vedic Extensions"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Phonetic Extensions"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Phonetic Extensions Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Combining Diacritical Marks Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended Additional"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Greek Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("General Punctuation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Superscripts and Subscripts"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Currency Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Combining Diacritical Marks for Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Letterlike Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Number Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arrows"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mathematical Operators"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Technical"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Control Pictures"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Optical Character Recognition"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Enclosed Alphanumerics"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Box Drawing"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Block Elements"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Geometric Shapes"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Dingbats"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Mathematical Symbols-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Arrows-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Braille Patterns"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Arrows-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Mathematical Symbols-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Mathematical Operators"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Symbols and Arrows"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Glagolitic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended-C"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Coptic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Georgian Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tifinagh"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ethiopic Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cyrillic Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Punctuation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Radicals Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kangxi Radicals"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ideographic Description Characters"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Symbols and Punctuation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hiragana"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Katakana"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bopomofo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hangul Compatibility Jamo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kanbun"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bopomofo Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Strokes"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Katakana Phonetic Extensions"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Enclosed CJK Letters and Months"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Compatibility"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Yijing Hexagram Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Yi Syllables"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Yi Radicals"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Lisu"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Vai"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cyrillic Extended-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bamum"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Modifier Tone Letters"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended-D"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Syloti Nagri"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Common Indic Number Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Phags-pa"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Saurashtra"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Devanagari Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kayah Li"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Rejang"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hangul Jamo Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Javanese"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Myanmar Extended-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cham"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Myanmar Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tai Viet"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Meetei Mayek Extensions"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ethiopic Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Latin Extended-E"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cherokee Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Meetei Mayek"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hangul Syllables"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hangul Jamo Extended-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("High Surrogates"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("High Private Use Surrogates"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Low Surrogates"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Private Use Area"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Compatibility Ideographs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Alphabetic Presentation Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic Presentation Forms-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Variation Selectors"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Vertical Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Combining Half Marks"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Compatibility Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Small Form Variants"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic Presentation Forms-B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Halfwidth and Fullwidth Forms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Specials"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Linear B Syllabary"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Linear B Ideograms"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Aegean Numbers"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ancient Greek Numbers"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ancient Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Phaistos Disc"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Lycian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Carian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Coptic Epact Numbers"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old Italic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Gothic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old Permic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ugaritic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old Persian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Deseret"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Shavian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Osmanya"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Osage"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Elbasan"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Caucasian Albanian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Linear A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cypriot Syllabary"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Imperial Aramaic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Palmyrene"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Nabataean"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Hatran"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Phoenician"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Lydian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Meroitic Hieroglyphs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Meroitic Cursive"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kharoshthi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old South Arabian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old North Arabian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Manichaean"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Avestan"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Inscriptional Parthian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Inscriptional Pahlavi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Psalter Pahlavi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old Turkic"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Old Hungarian"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Rumi Numeral Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Brahmi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kaithi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sora Sompeng"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Chakma"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mahajani"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sharada"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sinhala Archaic Numbers"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Khojki"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Multani"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Khudawadi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Grantha"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Newa"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tirhuta"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Siddham"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Modi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mongolian Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Takri"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ahom"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Warang Citi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Zanabazar Square"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Soyombo"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Pau Cin Hau"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bhaiksuki"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Marchen"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Masaram Gondi"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cuneiform"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Cuneiform Numbers and Punctuation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Early Dynastic Cuneiform"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Egyptian Hieroglyphs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Anatolian Hieroglyphs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bamum Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mro"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Bassa Vah"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Pahawh Hmong"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miao"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ideographic Symbols and Punctuation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tangut"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tangut Components"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kana Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Kana Extended-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Nushu"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Duployan"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Shorthand Format Controls"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Byzantine Musical Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Musical Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ancient Greek Musical Notation"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tai Xuan Jing Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Counting Rod Numerals"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mathematical Alphanumeric Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Sutton SignWriting"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Glagolitic Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mende Kikakui"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Adlam"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Arabic Mathematical Alphabetic Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Mahjong Tiles"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Domino Tiles"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Playing Cards"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Enclosed Alphanumeric Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Enclosed Ideographic Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Miscellaneous Symbols and Pictographs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Emoticons"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Ornamental Dingbats"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Transport and Map Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Alchemical Symbols"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Geometric Shapes Extended"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Arrows-C"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplemental Symbols and Pictographs"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension B"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension C"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension D"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension E"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Unified Ideographs Extension F"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("CJK Compatibility Ideographs Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Tags"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Variation Selectors Supplement"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplementary Private Use Area-A"),
+    /* TRANSLATORS: You might refer the translations from gucharmap with
+                    the following command:
+       msgmerge -C gucharmap.po ibus.po ibus.pot */
+    N_("Supplementary Private Use Area-B"),
+};
+#endif
diff --git a/src/unicode-parser.c b/src/unicode-parser.c
new file mode 100644
index 00000000..e98c6d5f
--- /dev/null
+++ b/src/unicode-parser.c
@@ -0,0 +1,502 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "ibusunicode.h"
+
+#define NAMES_LIST_SUBJECT "The Unicode Standard"
+#define BLOCKS_SUBJECT "Blocks-"
+
+/* This file has 21 lines about the license at the top of the file. */
+#define LICENSE_LINES 21
+
+typedef enum
+{
+    UCD_NAMES_LIST,
+    UCD_BLOCKS
+} UCDType;
+
+typedef struct _UnicodeData UnicodeData;
+typedef struct _UnicodeDataIndex UnicodeDataIndex;
+
+struct _UnicodeData{
+    gunichar code;
+    gchar   *name;
+    gchar   *alias;
+    gunichar start;
+    gunichar end;
+    GSList  *list;
+};
+
+struct _UnicodeDataIndex {
+    gchar *index;
+    UnicodeData *data_list;
+};
+
+static gchar *unicode_version;
+
+static void
+unicode_data_new_object (UnicodeData *data)
+{
+    g_return_if_fail (data != NULL);
+    if (!data->name) {
+        g_warning ("No name in U+%04X", data->code);
+    }
+    IBusUnicodeData *unicode =
+            ibus_unicode_data_new ("code",
+                                   data->code,
+                                   "name",
+                                   data->name ? g_strdup (data->name)
+                                           : g_strdup (""),
+                                   "alias",
+                                   data->alias ? g_strdup (data->alias)
+                                           : g_strdup (""),
+                                   NULL);
+    data->list = g_slist_append (data->list, unicode);
+}
+
+static void
+unicode_block_new_object (UnicodeData *data)
+{
+    g_return_if_fail (data != NULL);
+    if (!data->name) {
+        g_warning ("No name in U+%04X", data->start);
+    }
+    IBusUnicodeBlock *block =
+            ibus_unicode_block_new ("start",
+                                    data->start,
+                                    "end",
+                                    data->end,
+                                    "name",
+                                    data->name ? g_strdup (data->name)
+                                           : g_strdup (""),
+                                   NULL);
+    data->list = g_slist_append (data->list, block);
+}
+
+static void
+unicode_data_reset (UnicodeData *data)
+{
+    g_return_if_fail (data != NULL);
+    data->code = 0;
+    g_clear_pointer (&data->name, g_free);
+    g_clear_pointer (&data->alias, g_free);
+    data->start = 0;
+    data->end = 0;
+}
+
+static gboolean
+ucd_names_list_parse_comment (const gchar *line)
+{
+    static gboolean has_version = FALSE;
+
+    if (has_version)
+        return TRUE;
+    if (strlen (line) > 4 && strncmp (line, "@@@", 3) == 0) {
+        gchar **elements = g_strsplit (line, "\t", -1);
+        if (strncmp (elements[1], NAMES_LIST_SUBJECT,
+            strlen (NAMES_LIST_SUBJECT)) == 0) {
+            unicode_version =
+                    g_strdup (elements[1] + strlen (NAMES_LIST_SUBJECT) + 1);
+            has_version = TRUE;
+        }
+        g_strfreev (elements);
+    }
+    return TRUE;
+}
+
+static gboolean
+ucd_names_list_parse_alias (const gchar *line,
+                            UnicodeData *data)
+{
+    g_return_val_if_fail (line != NULL, FALSE);
+    g_return_val_if_fail (data != NULL, FALSE);
+
+    if (*line == '\0')
+        return FALSE;
+    data->alias = g_strdup (line);
+    return TRUE;
+}
+
+static gboolean
+ucd_names_list_parse_indent_line (const gchar *line,
+                                  UnicodeData *data)
+{
+    g_return_val_if_fail (line != NULL, FALSE);
+
+    switch (*line) {
+    case '\0':
+        return FALSE;
+    case '=':
+        line++;
+        while (*line == ' ') line++;
+        return ucd_names_list_parse_alias (line, data);
+    default:;
+    }
+    return TRUE;
+}
+
+static gboolean
+ucd_names_list_parse_line (const gchar *line,
+                           UnicodeData *data)
+{
+    g_return_val_if_fail (line != NULL, FALSE);
+
+    switch (*line) {
+    case '\0':
+        return TRUE;
+    case ';':
+        return TRUE;
+    case '@':
+        return ucd_names_list_parse_comment (line);
+    case '\t':
+        return ucd_names_list_parse_indent_line (line + 1, data);
+    default:;
+    }
+    if (g_ascii_isxdigit (*line)) {
+        gchar **elements = g_strsplit (line, "\t", -1);
+        gunichar code;
+        gchar *name;
+
+        if (g_strv_length (elements) < 2) {
+            g_strfreev (elements);
+            return FALSE;
+        }
+        code = g_ascii_strtoull (elements[0], NULL, 16);
+        name = g_strdup (elements[1]);
+        if (data->name) {
+            unicode_data_new_object (data);
+            unicode_data_reset (data);
+        }
+        data->code = code;
+        data->name = name;
+    }
+    return TRUE;
+}
+
+static gboolean
+ucd_blocks_parse_comment (const gchar *line)
+{
+    static gboolean has_version = FALSE;
+
+    g_return_val_if_fail (line != NULL, FALSE);
+
+    if (has_version)
+        return TRUE;
+    while (*line == ' ') line++;
+    if (strlen (line) > strlen (BLOCKS_SUBJECT) &&
+        strncmp (line, BLOCKS_SUBJECT, strlen (BLOCKS_SUBJECT)) == 0) {
+            unicode_version = g_strdup (line + strlen (BLOCKS_SUBJECT) + 1);
+            has_version = TRUE;
+    }
+    return TRUE;
+}
+
+static gboolean
+ucd_blocks_parse_line (const gchar *line,
+                       UnicodeData *data)
+{
+    g_return_val_if_fail (line != NULL, FALSE);
+
+    switch (*line) {
+    case '\0':
+        return TRUE;
+    case '#':
+        return ucd_blocks_parse_comment (line + 1);
+    default:;
+    }
+    if (g_ascii_isxdigit (*line)) {
+        gchar *endptr = NULL;
+        gunichar start = g_ascii_strtoull (line, &endptr, 16);
+        gunichar end;
+        gchar *name = NULL;
+
+        if (endptr == NULL || *endptr == '\0')
+            return FALSE;
+        while (*endptr == '.') endptr++;
+        line = endptr;
+        endptr = NULL;
+        end = g_ascii_strtoull (line, &endptr, 16);
+        if (endptr == NULL || *endptr == '\0')
+            return FALSE;
+        while (*endptr == ';') endptr++;
+        while (*endptr == ' ') endptr++;
+        if (*endptr == '\0')
+            return FALSE;
+        name = g_strdup (endptr);
+        if (data->name) {
+            unicode_block_new_object (data);
+            unicode_data_reset (data);
+        }
+        data->start = start;
+        data->end = end;
+        data->name = name;
+    }
+    return TRUE;
+}
+
+static gboolean
+ucd_parse_file (const gchar *filename,
+                GSList     **list,
+                UCDType      type)
+{
+    UnicodeData data = { 0, };
+    gchar *content = NULL;
+    gsize length = 0;
+    GError *error = NULL;
+    gchar *head, *end, *line;
+    int n = 1;
+
+    g_return_val_if_fail (filename != NULL, FALSE);
+    g_return_val_if_fail (list != NULL, FALSE);
+
+    if (!g_file_get_contents (filename, &content, &length, &error)) {
+        g_warning ("Failed to load %s: %s", filename, error->message);
+        goto failed_to_parse_ucd_names_list;
+    }
+    head = end = content;
+    while (*end == '\n' && end - content < length) {
+        end++;
+        n++;
+    }
+    head = end;
+    while (end - content < length) {
+        while (*end != '\n' && end - content < length)
+            end++;
+        if (end - content >= length)
+            break;
+        line = g_strndup (head, end - head);
+        switch (type) {
+        case UCD_NAMES_LIST:
+            if (!ucd_names_list_parse_line (line, &data)) {
+                g_warning ("parse error #%d in %s version %s: %s",
+                           n, filename,
+                           unicode_version ? unicode_version : "(null)",
+                           line);
+            }
+            break;
+        case UCD_BLOCKS:
+            if (!ucd_blocks_parse_line (line, &data)) {
+                g_warning ("parse error #%d in %s version %s: %s",
+                           n, filename,
+                           unicode_version ? unicode_version : "(null)",
+                           line);
+            }
+            break;
+        default:
+            g_abort ();
+        }
+        while (*end == '\n' && end - content < length) {
+            end++;
+            n++;
+        }
+        g_free (line);
+        head = end;
+    }
+    if (data.name != NULL) {
+        switch (type) {
+        case UCD_NAMES_LIST:
+            unicode_data_new_object (&data);
+            break;
+        case UCD_BLOCKS:
+            unicode_block_new_object (&data);
+            break;
+        default:;
+        }
+        unicode_data_reset (&data);
+    }
+    g_free (content);
+    *list = data.list;
+    return TRUE;
+
+failed_to_parse_ucd_names_list:
+    if (error)
+        g_error_free (error);
+    g_clear_pointer (&content, g_free);
+    *list = data.list;
+    return FALSE;
+}
+
+static void
+block_list_dump (IBusUnicodeBlock *block,
+                 GString          *buff)
+{
+    g_return_if_fail (buff != NULL);
+
+    g_string_append (buff, "    /* TRANSLATORS: You might refer the "         \
+                           "translations from gucharmap with\n"               \
+                           "                    the following command:\n"     \
+                           "       msgmerge -C gucharmap.po ibus.po "         \
+                           "ibus.pot */\n");
+    gchar *line = g_strdup_printf ("    N_(\"%s\"),\n",
+                                   ibus_unicode_block_get_name (block));
+    g_string_append (buff, line);
+}
+
+static void
+ucd_block_translatable_save (const gchar *filename,
+                             GSList      *blocks_list)
+{
+    gchar *content = NULL;
+    gsize length = 0;
+    GError *error = NULL;
+    gchar *p;
+    GString *buff = NULL;
+    int i;
+    GSList *list = blocks_list;
+
+    g_return_if_fail (filename != NULL);
+    g_return_if_fail (list != NULL);
+
+    if (!g_file_get_contents (__FILE__, &content, &length, &error)) {
+        g_warning ("Failed to load %s: %s", __FILE__, error->message);
+        g_clear_pointer (&error, g_error_free);
+        return;
+    }
+
+    buff = g_string_new (NULL);
+    p = content;
+    for (i = 0; i < LICENSE_LINES; i++, p++) {
+        if ((p = strchr (p, '\n')) == NULL)
+            break;
+    }
+    if (p != NULL) {
+        g_string_append (buff, g_strndup (content, p - content));
+        g_string_append_c (buff, '\n');
+    }
+    g_clear_pointer (&content, g_free);
+
+    g_string_append (buff, g_strdup ("\n"));
+    g_string_append (buff, g_strdup_printf ("/* This file is generated by %s. */", __FILE__));
+    g_string_append (buff, g_strdup ("\n"));
+    g_string_append (buff, g_strdup ("include <glib/gi18n.h>\n"));
+    g_string_append (buff, g_strdup ("\n"));
+    g_string_append (buff, g_strdup ("#ifndef __IBUS_UNICODE_GEN_H_\n"));
+    g_string_append (buff, g_strdup ("#define __IBUS_UNICODE_GEN_H_\n"));
+    g_string_append (buff, g_strdup ("const static char *unicode_blocks[] = {\n"));
+    g_slist_foreach (list, (GFunc)block_list_dump, buff);
+    g_string_append (buff, g_strdup ("};\n"));
+    g_string_append (buff, g_strdup ("#endif\n"));
+
+    if (!g_file_set_contents (filename, buff->str, -1, &error)) {
+        g_warning ("Failed to save emoji category file %s: %s", filename, error->message);
+        g_error_free (error);
+    }
+
+    g_string_free (buff, TRUE);
+}
+
+int
+main (int argc, char *argv[])
+{
+    gchar *prgname;
+    gchar *input_names_list = NULL;
+    gchar *input_blocks = NULL;
+    gchar *output_names_list = NULL;
+    gchar *output_blocks = NULL;
+    gchar *output_blocks_trans = NULL;
+    GOptionEntry     entries[] = {
+        { "input-names-list", 'n', 0, G_OPTION_ARG_STRING, &input_names_list,
+          "Parse NamesList.txt FILE in unicode.org ",
+          "FILE"
+        },
+        { "input-blocks", 'b', 0, G_OPTION_ARG_STRING, &input_blocks,
+          "Parse Blocks.txt FILE in unicode.org ",
+          "FILE"
+        },
+        { "output-names-list", 'o', 0, G_OPTION_ARG_STRING, &output_names_list,
+          "Save the Unicode data as FILE",
+          "FILE"
+        },
+        { "output-blocks", 'B', 0, G_OPTION_ARG_STRING, &output_blocks,
+          "Save the Unicode block list as FILE",
+          "FILE"
+        },
+        { "output-blocks-trans", 'C', 0, G_OPTION_ARG_STRING,
+          &output_blocks_trans,
+          "Save the translatable Unicode blocks as FILE",
+          "FILE"
+        },
+        { NULL }
+    };
+    GOptionContext *context;
+    GError *error = NULL;
+    GSList *names_list = NULL;
+    GSList *blocks_list = NULL;
+
+#ifdef HAVE_LOCALE_H
+    /* To output emoji warnings. */
+    setlocale (LC_ALL, "");
+#endif
+
+    prgname = g_path_get_basename (argv[0]);
+    g_set_prgname (prgname);
+    g_free (prgname);
+
+    context = g_option_context_new (NULL);
+    g_option_context_add_main_entries (context, entries, NULL);
+
+    if (argc < 3) {
+        g_print ("%s", g_option_context_get_help (context, TRUE, NULL));
+        g_option_context_free (context);
+        return -1;
+    }
+
+    if (!g_option_context_parse (context, &argc, &argv, &error)) {
+        g_warning ("Failed options: %s", error->message);
+        g_error_free (error);
+        return -1;
+    }
+    g_option_context_free (context);
+
+    if (input_names_list) {
+        ucd_parse_file (input_names_list, &names_list, UCD_NAMES_LIST);
+        g_free (input_names_list);
+    }
+    if (output_names_list && names_list)
+        ibus_unicode_data_save (output_names_list, names_list);
+    g_free (output_names_list);
+
+    if (input_blocks) {
+        ucd_parse_file (input_blocks, &blocks_list, UCD_BLOCKS);
+        g_free (input_blocks);
+    }
+    if (output_blocks && blocks_list)
+        ibus_unicode_block_save (output_blocks, blocks_list);
+    if (output_blocks_trans && blocks_list)
+        ucd_block_translatable_save (output_blocks_trans, blocks_list);
+    g_free (output_blocks);
+
+    g_free (unicode_version);
+    return 0;
+}
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index f3e9f15c..555ea68f 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -2,7 +2,7 @@
  *
  * ibus - The Input Bus
  *
- * Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,7 +20,7 @@
  * USA
  */
 
-class IBusEmojier : Gtk.ApplicationWindow {
+public class IBusEmojier : Gtk.ApplicationWindow {
     private class EEntry : Gtk.SearchEntry {
         public EEntry() {
             GLib.Object(
@@ -99,15 +99,70 @@ class IBusEmojier : Gtk.ApplicationWindow {
         }
     }
     private class EWhiteLabel : Gtk.Label {
+        private int m_minimum_width = 0;
+        private int m_natural_width = 0;
+        private int m_minimum_height = 0;
+        private int m_natural_height = 0;
         public EWhiteLabel(string text) {
             GLib.Object(
                 name : "IBusEmojierWhiteLabel"
             );
-            if (text != "")
-                set_label(text);
+            set_label(text);
+        }
+        public override void get_preferred_width(out int minimum_width,
+                                                 out int natural_width) {
+            if (m_minimum_height == 0 && m_natural_height == 0) {
+                base.get_preferred_height(out m_minimum_height,
+                                          out m_natural_height);
+            }
+            var text = get_label();
+            var ch = text.get_char();
+            if (text.length == 1 && ch == '\t') {
+                m_minimum_width = minimum_width = m_minimum_height;
+                m_natural_width = natural_width = m_natural_height;
+                return;
+            }
+            base.get_preferred_width(out minimum_width, out natural_width);
+            if (text.length == 1 && (ch == '\n' || ch == '\r')) {
+                minimum_width /= 2;
+                natural_width /= 2;
+                m_minimum_width = minimum_width;
+                m_natural_width = natural_width;
+                return;
+            }
+            if (minimum_width < m_minimum_height)
+                minimum_width = m_minimum_height;
+            if (natural_width < m_natural_height)
+                natural_width = m_natural_height;
+            m_minimum_width = minimum_width;
+            m_natural_width = natural_width;
+        }
+        public override void get_preferred_height(out int minimum_height,
+                                                  out int natural_height) {
+            if (m_minimum_width == 0 && m_natural_width == 0) {
+                base.get_preferred_width(out m_minimum_width,
+                                         out m_natural_width);
+            }
+            var text = get_label();
+            var ch = text.get_char();
+            if (text.length == 1 && ch == '\v') {
+                m_minimum_height = minimum_height = m_minimum_width;
+                m_natural_height = natural_height = m_natural_width;
+                return;
+            }
+            base.get_preferred_height(out minimum_height, out natural_height);
+            if (text.length == 1 && (ch == '\n' || ch == '\r')) {
+                minimum_height /= 2;
+                natural_height /= 2;
+                m_minimum_height = minimum_height;
+                m_natural_height = natural_height;
+                return;
+            }
+            m_minimum_height = minimum_height;
+            m_natural_height = natural_height;
         }
     }
-    private class ESelectedLabel : Gtk.Label {
+    private class ESelectedLabel : EWhiteLabel {
         public ESelectedLabel(string text) {
             GLib.Object(
                 name : "IBusEmojierSelectedLabel"
@@ -116,7 +171,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 set_label(text);
         }
     }
-    private class EGoldLabel : Gtk.Label {
+    private class EGoldLabel : EWhiteLabel {
         public EGoldLabel(string text) {
             GLib.Object(
                 name : "IBusEmojierGoldLabel"
@@ -167,6 +222,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
     }
     private class ETitleLabelBox : Gtk.HeaderBar {
         private Gtk.Label m_lang_label;
+        private Gtk.Label m_title_label;
 
         public ETitleLabelBox(string title) {
             GLib.Object(
@@ -177,9 +233,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
             );
             var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
             set_custom_title(vbox);
-            var label = new Gtk.Label(title);
-            label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE);
-            vbox.pack_start(label, true, false, 0);
+            m_title_label = new Gtk.Label(title);
+            m_title_label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE);
+            vbox.pack_start(m_title_label, true, false, 0);
             m_lang_label = new Gtk.Label(null);
             m_lang_label.get_style_context().add_class(
                     Gtk.STYLE_CLASS_SUBTITLE);
@@ -194,10 +250,19 @@ class IBusEmojier : Gtk.ApplicationWindow {
             menu_button.set_tooltip_text(_("Menu"));
             pack_end(menu_button);
         }
+        public new void set_title(string title) {
+            m_title_label.set_text(title);
+        }
         public void set_lang_label(string str) {
             m_lang_label.set_text(str);
         }
     }
+    private class LoadProgressObject : GLib.Object {
+        public LoadProgressObject() {
+        }
+        public signal void deserialize_unicode(uint done, uint total);
+    }
+
 
     private enum TravelDirection {
         NONE,
@@ -207,6 +272,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private const uint EMOJI_GRID_PAGE = 10;
     private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites");
     private const string EMOJI_CATEGORY_OTHERS = N_("Others");
+    private const string EMOJI_CATEGORY_UNICODE = N_("Open Unicode choice");
     private const unichar[] EMOJI_VARIANT_LIST = {
             0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff, 0x200d };
 
@@ -223,6 +289,8 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private static uint m_partial_match_length;
     private static uint m_partial_match_condition;
     private static bool m_show_emoji_variant = false;
+    private static int m_default_window_width;
+    private static int m_default_window_height;
     private static GLib.HashTable<string, GLib.SList<string>>?
             m_annotation_to_emojis_dict;
     private static GLib.HashTable<string, IBus.EmojiData>?
@@ -231,6 +299,14 @@ class IBusEmojier : Gtk.ApplicationWindow {
             m_category_to_emojis_dict;
     private static GLib.HashTable<string, GLib.SList<string>>?
             m_emoji_to_emoji_variants_dict;
+    private static GLib.HashTable<unichar, IBus.UnicodeData>?
+            m_unicode_to_data_dict;
+    private static GLib.HashTable<string, GLib.SList<unichar>>?
+            m_name_to_unicodes_dict;
+    private static GLib.SList<IBus.UnicodeBlock> m_unicode_block_list;
+    private static bool m_show_unicode = false;
+    private static LoadProgressObject m_unicode_progress_object;
+    private static bool m_loaded_unicode = false;
 
     private ThemedRGBA m_rgba;
     private Gtk.Box m_vbox;
@@ -246,7 +322,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private string? m_result;
     private string? m_unicode_point = null;
     private bool m_candidate_panel_is_visible;
-    private int m_category_active_index;
+    private int m_category_active_index = -1;
     private IBus.LookupTable m_lookup_table;
     private Gtk.Label[] m_candidates;
     private bool m_enter_notify_enable = true;
@@ -254,6 +330,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private uint m_entry_notify_disable_id;
     protected static double m_mouse_x;
     protected static double m_mouse_y;
+    private Gtk.ProgressBar m_unicode_progress_bar;
+    private Gtk.Label m_unicode_percent_label;
+    private double m_unicode_percent;
 
     public signal void candidate_clicked(uint index, uint button, uint state);
 
@@ -402,6 +481,8 @@ class IBusEmojier : Gtk.ApplicationWindow {
         if (m_annotation_to_emojis_dict == null) {
             reload_emoji_dict();
         }
+
+        get_load_progress_object();
     }
 
 
@@ -433,6 +514,13 @@ class IBusEmojier : Gtk.ApplicationWindow {
         m_emoji_to_emoji_variants_dict =
                 new GLib.HashTable<string, GLib.SList<string>>(GLib.str_hash,
                                                                GLib.str_equal);
+        m_unicode_to_data_dict =
+                new GLib.HashTable<unichar, IBus.UnicodeData>(
+                        GLib.direct_hash,
+                        GLib.direct_equal);
+        m_name_to_unicodes_dict =
+                new GLib.HashTable<string, GLib.SList<unichar>>(GLib.str_hash,
+                                                                GLib.str_equal);
     }
 
 
@@ -482,6 +570,10 @@ class IBusEmojier : Gtk.ApplicationWindow {
     private static string utf8_code_point(string str) {
         var buff = new GLib.StringBuilder();
         int length = str.char_count();
+        if (length == 0) {
+            buff.append("U+%04X".printf(0));
+            return buff.str;
+        }
         for (int i = 0; i < length; i++) {
             unichar ch = str.get_char(0);
             if (i == 0)
@@ -644,6 +736,72 @@ class IBusEmojier : Gtk.ApplicationWindow {
     }
 
 
+    private static void make_unicode_block_dict() {
+        m_unicode_block_list = IBus.UnicodeBlock.load(
+                    Config.PKGDATADIR + "/dicts/unicode-blocks.dict");
+        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
+            unowned string name = block.get_name();
+            if (m_emoji_max_seq_len < name.length)
+                m_emoji_max_seq_len = name.length;
+        }
+    }
+
+
+    private static void make_unicode_name_dict(Object source_object) {
+        IBus.UnicodeData.load_async(
+                    Config.PKGDATADIR + "/dicts/unicode-names.dict",
+                    source_object,
+                    null,
+        (IBus.UnicodeDataLoadAsyncFinish)make_unicode_name_dict_finish);
+    }
+
+    private static void
+    make_unicode_name_dict_finish(GLib.SList<IBus.UnicodeData> unicode_list) {
+        if (unicode_list == null)
+            return;
+        foreach (IBus.UnicodeData data in unicode_list) {
+            update_unicode_to_data_dict(data);
+            update_name_to_unicodes_dict(data);
+        }
+        GLib.List<unowned string> names =
+                m_name_to_unicodes_dict.get_keys();
+        foreach (unowned string name in names) {
+            if (m_emoji_max_seq_len < name.length)
+                m_emoji_max_seq_len = name.length;
+        }
+        m_loaded_unicode = true;
+    }
+
+
+    private static void update_unicode_to_data_dict(IBus.UnicodeData data) {
+        unichar code = data.get_code();
+        m_unicode_to_data_dict.replace(code, data);
+    }
+
+
+    private static void update_name_to_unicodes_dict(IBus.UnicodeData data) {
+        unichar code = data.get_code();
+        string[] names = {data.get_name().down(), data.get_alias().down()};
+        foreach (unowned string name in names) {
+            if (name == "")
+                continue;
+            bool has_code = false;
+            GLib.SList<unichar> hits =
+                    m_name_to_unicodes_dict.lookup(name).copy();
+            foreach (unichar hit_code in hits) {
+                if (hit_code == code) {
+                    has_code = true;
+                    break;
+                }
+            }
+            if (!has_code) {
+                hits.append(code);
+                m_name_to_unicodes_dict.replace(name, hits.copy());
+            }
+        }
+    }
+
+
     private void set_fixed_size() {
         resize(20, 1);
     }
@@ -665,6 +823,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
         m_scrolled_window = new EScrolledWindow();
         set_fixed_size();
 
+        m_title.set_title(_("Emoji Choice"));
         string language =
             IBus.get_language_name(m_current_lang_id);
         m_title.set_lang_label(language);
@@ -677,12 +836,12 @@ class IBusEmojier : Gtk.ApplicationWindow {
         Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
         m_list_box.set_adjustment(adjustment);
         m_list_box.row_activated.connect((box, gtkrow) => {
-            m_category_active_index = 0;
+            m_category_active_index = -1;
             EBoxRow row = gtkrow as EBoxRow;
             show_emoji_for_category(row.text);
         });
 
-        uint n = 1;
+        uint n = 0;
         if (m_favorites.length > 0) {
             EBoxRow row = new EBoxRow(EMOJI_CATEGORY_FAVORITES);
             EPaddedLabelBox widget =
@@ -716,9 +875,19 @@ class IBusEmojier : Gtk.ApplicationWindow {
             if (n++ == m_category_active_index)
                 m_list_box.select_row(row);
         }
+        if (m_unicode_block_list.length() > 0) {
+            EBoxRow row = new EBoxRow(EMOJI_CATEGORY_UNICODE);
+            EPaddedLabelBox widget =
+                    new EPaddedLabelBox(_(EMOJI_CATEGORY_UNICODE),
+                                        Gtk.Align.CENTER);
+            row.add(widget);
+            m_list_box.add(row);
+            if (n++ == m_category_active_index)
+                m_list_box.select_row(row);
+        }
 
         m_scrolled_window.show_all();
-        if (m_category_active_index == 0)
+        if (m_category_active_index == -1)
             m_list_box.unselect_all();
         m_list_box.invalidate_filter();
         m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
@@ -733,6 +902,11 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 m_lookup_table.append_candidate(text);
             }
             m_backward = category;
+        } else if (category == EMOJI_CATEGORY_UNICODE) {
+            m_category_active_index = -1;
+            m_show_unicode = true;
+            show_unicode_blocks();
+            return;
         } else {
             unowned GLib.SList<unowned string> emojis =
                     m_category_to_emojis_dict.lookup(category);
@@ -764,6 +938,126 @@ class IBusEmojier : Gtk.ApplicationWindow {
     }
 
 
+    private void show_unicode_blocks() {
+        m_show_unicode = true;
+        if (m_default_window_width == 0 && m_default_window_height == 0)
+            get_size(out m_default_window_width, out m_default_window_height);
+        remove_all_children();
+        set_fixed_size();
+
+        m_title.set_title(_("Unicode Choice"));
+        EPaddedLabelBox label =
+                new EPaddedLabelBox(_("Bring back emoji choice"),
+                                    Gtk.Align.CENTER,
+                                    TravelDirection.BACKWARD);
+        Gtk.Button button = new Gtk.Button();
+        button.add(label);
+        m_vbox.add(button);
+        button.show_all();
+        button.button_press_event.connect((w, e) => {
+            m_category_active_index = -1;
+            m_show_unicode = false;
+            hide_candidate_panel();
+            return true;
+        });
+        m_scrolled_window = new EScrolledWindow();
+        m_title.set_lang_label("");
+        m_vbox.add(m_scrolled_window);
+        Gtk.Viewport viewport = new Gtk.Viewport(null, null);
+        m_scrolled_window.add(viewport);
+
+        m_list_box = new EListBox();
+        viewport.add(m_list_box);
+        Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
+        m_list_box.set_adjustment(adjustment);
+        m_list_box.row_activated.connect((box, gtkrow) => {
+            m_category_active_index = -1;
+            EBoxRow row = gtkrow as EBoxRow;
+            show_unicode_for_block(row.text);
+        });
+
+        uint n = 0;
+        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
+            string name = block.get_name();
+            EBoxRow row = new EBoxRow(name);
+            EPaddedLabelBox widget =
+                    new EPaddedLabelBox(_(name), Gtk.Align.CENTER);
+            row.add(widget);
+            m_list_box.add(row);
+            if (n++ == m_category_active_index) {
+                m_list_box.select_row(row);
+            }
+        }
+
+        set_size_request(-1, m_default_window_height + 100);
+        m_scrolled_window.set_policy(Gtk.PolicyType.NEVER,
+                                     Gtk.PolicyType.AUTOMATIC);
+        m_scrolled_window.show_all();
+        if (m_category_active_index == -1)
+            m_list_box.unselect_all();
+        m_list_box.invalidate_filter();
+        m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
+    }
+
+    private void show_unicode_for_block(string block_name) {
+        if (!m_loaded_unicode) {
+            remove_all_children();
+            set_fixed_size();
+            m_unicode_progress_bar = new Gtk.ProgressBar();
+            m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
+            m_unicode_progress_bar.set_halign(Gtk.Align.CENTER);
+            m_unicode_progress_bar.set_valign(Gtk.Align.CENTER);
+            m_vbox.add(m_unicode_progress_bar);
+            m_unicode_progress_bar.show();
+            var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
+            hbox.set_halign(Gtk.Align.CENTER);
+            hbox.set_valign(Gtk.Align.CENTER);
+            m_vbox.add(hbox);
+            var label = new Gtk.Label(_("Loading a Unicode dictionary:"));
+            hbox.pack_start(label, false, true, 0);
+            m_unicode_percent_label = new Gtk.Label("");
+            hbox.pack_start(m_unicode_percent_label, false, true, 0);
+            hbox.show_all();
+
+            m_unicode_progress_object.deserialize_unicode.connect((i, n) => {
+                m_unicode_percent = (double)i / n;
+            });
+            GLib.Timeout.add(100, () => {
+                m_unicode_progress_bar.set_fraction(m_unicode_percent);
+                m_unicode_percent_label.set_text(
+                        "%.0f%%\n".printf(m_unicode_percent * 100));
+                m_unicode_progress_bar.show();
+                m_unicode_percent_label.show();
+                if (m_loaded_unicode) {
+                    show_unicode_for_block(block_name);
+                }
+                return !m_loaded_unicode;
+            });
+            return;
+        }
+        unichar start = 0;
+        unichar end = 0;
+        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
+            string name = block.get_name();
+            if (block_name == name) {
+                start = block.get_start();
+                end = block.get_end();
+            }
+        }
+        m_lookup_table.clear();
+        for (unichar ch = start; ch < end; ch++) {
+            unowned IBus.UnicodeData? data =
+                    m_unicode_to_data_dict.lookup(ch);
+            if (data == null)
+                continue;
+            IBus.Text text = new IBus.Text.from_unichar(ch);
+            m_lookup_table.append_candidate(text);
+        }
+        m_backward = block_name;
+        show_candidate_panel();
+    }
+
+
     private void show_arrow_buttons() {
         Gtk.Button next_button = new Gtk.Button();
         next_button.clicked.connect(() => {
@@ -840,6 +1134,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
     lookup_emojis_from_annotation(string annotation) {
         GLib.SList<string>? total_emojis = null;
         unowned GLib.SList<string>? sub_emojis = null;
+        unowned GLib.SList<unichar>? sub_unicodes = null;
         int length = annotation.length;
         if (m_has_partial_match && length >= m_partial_match_length) {
             foreach (unowned string key in
@@ -877,6 +1172,22 @@ class IBusEmojier : Gtk.ApplicationWindow {
             foreach (unowned string emoji in sub_emojis)
                 total_emojis.append(emoji);
         }
+        if (length >= m_partial_match_length) {
+            foreach (unowned string key in m_name_to_unicodes_dict.get_keys()) {
+                bool matched = false;
+                if (key.index_of(annotation) >= 0)
+                        matched = true;
+                if (!matched)
+                    continue;
+                sub_unicodes = m_name_to_unicodes_dict.lookup(key);
+                foreach (unichar code in sub_unicodes) {
+                    string ch = code.to_string();
+                    if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
+                        total_emojis.append(ch);
+                    }
+                }
+            }
+        }
         return total_emojis;
     }
 
@@ -1049,58 +1360,99 @@ class IBusEmojier : Gtk.ApplicationWindow {
             grid.show_all();
             string text = m_lookup_table.get_candidate(cursor).text;
             unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text);
-            if (data == null) {
-                // TODO: Provide a custom description and annotation for
-                // the favorite emojis.
-                EPaddedLabelBox widget = new EPaddedLabelBox(
-                        _("Description: %s").printf(_("None")),
-                        Gtk.Align.START);
-                m_vbox.add(widget);
-                widget.show_all();
-                show_code_point_description(text);
+            if (data != null) {
+                show_emoji_description(data, text);
                 return;
-            } else {
-                unowned string description = data.get_description();
-                EPaddedLabelBox widget = new EPaddedLabelBox(
-                        _("Description: %s").printf(description),
-                        Gtk.Align.START);
-                m_vbox.add(widget);
-                widget.show_all();
             }
-            unowned GLib.SList<unowned string>? annotations =
-                    data.get_annotations();
-            var buff = new GLib.StringBuilder();
-            int i = 0;
-            foreach (unowned string annotation in annotations) {
-                if (i++ == 0)
-                    buff.append_printf(_("Annotations: %s"), annotation);
-                else
-                    buff.append_printf(" | %s", annotation);
-                if (buff.str.char_count() > 30) {
-                    EPaddedLabelBox widget =
-                            new EPaddedLabelBox(buff.str,
-                                                Gtk.Align.START);
-                    m_vbox.add(widget);
-                    widget.show_all();
-                    buff.erase();
+            if (text.char_count() <= 1) {
+                unichar code = text.get_char();
+                unowned IBus.UnicodeData? udata =
+                        m_unicode_to_data_dict.lookup(code);
+                if (udata != null) {
+                    show_unicode_description(udata, text);
+                    return;
                 }
             }
-            if (buff.str != "") {
-                EPaddedLabelBox widget = new EPaddedLabelBox(buff.str,
-                                                             Gtk.Align.START);
+            // TODO: Provide a custom description and annotation for
+            // the favorite emojis.
+            EPaddedLabelBox widget = new EPaddedLabelBox(
+                        _("Description: %s").printf(_("None")),
+                        Gtk.Align.START);
+            m_vbox.add(widget);
+            widget.show_all();
+            show_code_point_description(text);
+        }
+    }
+
+
+    private void show_emoji_description(IBus.EmojiData data,
+                                        string         text) {
+        unowned string description = data.get_description();
+        {
+            EPaddedLabelBox widget = new EPaddedLabelBox(
+                    _("Description: %s").printf(description),
+                    Gtk.Align.START);
+            m_vbox.add(widget);
+            widget.show_all();
+        }
+        unowned GLib.SList<unowned string>? annotations =
+                data.get_annotations();
+        var buff = new GLib.StringBuilder();
+        int i = 0;
+        foreach (unowned string annotation in annotations) {
+            if (i++ == 0)
+                buff.append_printf(_("Annotations: %s"), annotation);
+            else
+                buff.append_printf(" | %s", annotation);
+            if (buff.str.char_count() > 30) {
+                EPaddedLabelBox widget =
+                        new EPaddedLabelBox(buff.str,
+                                            Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
+                buff.erase();
             }
-            show_code_point_description(text);
         }
+        if (buff.str != "") {
+            EPaddedLabelBox widget = new EPaddedLabelBox(buff.str,
+                                                         Gtk.Align.START);
+            m_vbox.add(widget);
+            widget.show_all();
+        }
+        show_code_point_description(text);
+    }
+
+    private void show_unicode_description(IBus.UnicodeData data,
+                                          string           text) {
+        unowned string name = data.get_name();
+        {
+            EPaddedLabelBox widget = new EPaddedLabelBox(
+                    _("Name: %s").printf(name),
+                    Gtk.Align.START);
+            m_vbox.add(widget);
+            widget.show_all();
+        }
+        unowned string alias = data.get_alias();
+        {
+            EPaddedLabelBox widget = new EPaddedLabelBox(
+                    _("Alias: %s").printf(alias),
+                    Gtk.Align.START);
+            m_vbox.add(widget);
+            widget.show_all();
+        }
+        show_code_point_description(text);
     }
 
 
     private void hide_candidate_panel() {
         m_enter_notify_enable = true;
         m_candidate_panel_is_visible = false;
-        if (m_loop.is_running())
-            show_category_list();
+        if (m_loop.is_running()) {
+            if (m_show_unicode)
+                show_unicode_blocks();
+            else
+                show_category_list();
+        }
     }
 
 
@@ -1165,19 +1517,41 @@ class IBusEmojier : Gtk.ApplicationWindow {
     }
 
 
-    private void category_list_cursor_move(uint keyval) {
+    private bool category_list_cursor_move(uint keyval) {
         GLib.List<weak Gtk.Widget> list = m_list_box.get_children();
-        if (keyval == Gdk.Key.Down) {
-            m_category_active_index =
-                    ++m_category_active_index % ((int)list.length() + 1);
-        } else if (keyval == Gdk.Key.Up) {
+        int length = (int)list.length();
+        if (length == 0)
+            return false;
+        switch(keyval) {
+        case Gdk.Key.Down:
+            if (++m_category_active_index == length)
+                m_category_active_index = 0;
+            break;
+        case Gdk.Key.Up:
             if (--m_category_active_index < 0)
-                    m_category_active_index = (int)list.length();
+                    m_category_active_index = length - 1;
+            break;
+        case Gdk.Key.Home:
+            m_category_active_index = 0;
+            break;
+        case Gdk.Key.End:
+            m_category_active_index = length - 1;
+            break;
         }
-        Gtk.Adjustment adjustment = m_list_box.get_adjustment();
-        m_scrolled_window.set_hadjustment(new Gtk.Adjustment(0, 0, 0, 0, 0, 0));
-        m_scrolled_window.set_vadjustment(adjustment);
-        show_category_list();
+        var row = m_list_box.get_selected_row();
+        if (row != null)
+            m_list_box.unselect_row(row);
+        if (m_category_active_index >= 0) {
+            row = m_list_box.get_row_at_index(m_category_active_index);
+            m_list_box.select_row(row);
+        } else {
+            row = m_list_box.get_row_at_index(0);
+        }
+        Gtk.Allocation alloc = { 0, 0, 0, 0 };
+        row.get_allocation(out alloc);
+        var adjustment = m_scrolled_window.get_vadjustment();
+        adjustment.clamp_page(alloc.y, alloc.y + alloc.height);
+        return true;
     }
 
 
@@ -1211,7 +1585,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
                 keyval = Gdk.Key.Up;
             else if (keyval == Gdk.Key.Right)
                 keyval = Gdk.Key.Down;
-            category_list_cursor_move(keyval);
+            return category_list_cursor_move(keyval);
         }
         return true;
     }
@@ -1227,7 +1601,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
             else if (keyval == Gdk.Key.Up)
                 candidate_panel_cursor_up();
         } else {
-            category_list_cursor_move(keyval);
+            return category_list_cursor_move(keyval);
         }
         return true;
     }
@@ -1262,12 +1636,25 @@ class IBusEmojier : Gtk.ApplicationWindow {
                             ? true : false);
             return true;
         }
+        if (!m_candidate_panel_is_visible)
+            return category_list_cursor_move(keyval);
         return false;
     }
 
 
     private bool key_press_escape() {
-        if (m_backward_index >= 0 && m_backward != null) {
+        if (m_show_unicode) {
+            if (m_candidate_panel_is_visible) {
+                m_candidate_panel_is_visible = false;
+                show_unicode_blocks();
+                return true;
+            } else {
+                m_show_unicode = false;
+                m_category_active_index = -1;
+                hide_candidate_panel();
+                return true;
+            }
+        } else if (m_backward_index >= 0 && m_backward != null) {
             show_emoji_for_category(m_backward);
             return true;
         } else if (m_candidate_panel_is_visible) {
@@ -1287,10 +1674,13 @@ class IBusEmojier : Gtk.ApplicationWindow {
         if (m_candidate_panel_is_visible) {
             uint index = m_lookup_table.get_cursor_pos();
             candidate_panel_select_index(index);
-        } else if (m_category_active_index > 0) {
+        } else if (m_category_active_index >= 0) {
             Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row();
             EBoxRow row = gtkrow as EBoxRow;
-            show_emoji_for_category(row.text);
+            if (m_show_unicode)
+                show_unicode_for_block(row.text);
+            else
+                show_emoji_for_category(row.text);
         }
         return true;
     }
@@ -1380,6 +1770,7 @@ class IBusEmojier : Gtk.ApplicationWindow {
         m_candidate_panel_is_visible = false;
         m_result = null;
         m_enter_notify_enable = true;
+        m_show_unicode = false;
 
         /* Let gtk recalculate the window size. */
         resize(1, 1);
@@ -1399,8 +1790,9 @@ class IBusEmojier : Gtk.ApplicationWindow {
          * prevention logic:
          * https://mail.gnome.org/archives/gtk-devel-list/2017-May/msg00026.html
          */
-        uint32 timestamp = event.get_time();
-        present_with_time(timestamp);
+        //uint32 timestamp = event.get_time();
+        //present_with_time(timestamp);
+        present_centralize(event);
 
         Gdk.Device pointer;
 #if VALA_0_34
@@ -1646,18 +2038,24 @@ class IBusEmojier : Gtk.ApplicationWindow {
         Gtk.Allocation allocation;
         get_allocation(out allocation);
         Gdk.Rectangle monitor_area;
+        Gdk.Rectangle work_area;
 #if VALA_0_34
         Gdk.Display display = Gdk.Display.get_default();
         Gdk.Monitor monitor = display.get_monitor_at_window(this.get_window());
         monitor_area = monitor.get_geometry();
+        work_area = monitor.get_workarea();
 #else
         Gdk.Screen screen = Gdk.Screen.get_default();
         int monitor_num = screen.get_monitor_at_window(this.get_window());
         screen.get_monitor_geometry(monitor_num, out monitor_area);
+        work_area = screen.get_monitor_workarea(monitor_num);
 #endif
         int x = (monitor_area.x + monitor_area.width - allocation.width)/2;
         int y = (monitor_area.y + monitor_area.height
                  - allocation.height)/2;
+        // Do not hide a bottom panel in XFCE4
+        if (work_area.y < y)
+            y = work_area.y;
         move(x, y);
 
         uint32 timestamp = event.get_time();
@@ -1723,7 +2121,6 @@ class IBusEmojier : Gtk.ApplicationWindow {
             string? favorite = unowned_favorites[i];
             // Avoid gsetting value error by manual setting
             GLib.return_if_fail(favorite != null);
-            GLib.return_if_fail(favorite != "");
             m_favorites += favorite;
         }
         for(int i = 0; i < unowned_favorite_annotations.length; i++) {
@@ -1733,4 +2130,19 @@ class IBusEmojier : Gtk.ApplicationWindow {
         }
         update_favorite_emoji_dict();
     }
+
+
+    private static GLib.Object get_load_progress_object() {
+            if (m_unicode_progress_object == null)
+                m_unicode_progress_object = new LoadProgressObject();
+            return m_unicode_progress_object as GLib.Object;
+    }
+
+
+    public static void load_unicode_dict() {
+        if (m_unicode_block_list.length() == 0)
+            make_unicode_block_dict();
+        if (m_name_to_unicodes_dict.size() == 0)
+            make_unicode_name_dict(IBusEmojier.get_load_progress_object());
+    }
 }
diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
index 6615f22b..d816352e 100644
--- a/ui/gtk3/emojierapp.vala
+++ b/ui/gtk3/emojierapp.vala
@@ -176,6 +176,8 @@ public class EmojiApplication : Application {
                 m_settings_emoji.get_strv("favorites"),
                 m_settings_emoji.get_strv("favorite-annotations"));
 
+        IBusEmojier.load_unicode_dict();
+
         activate_dialog(command_line);
 
         return Posix.EXIT_SUCCESS;
diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h
index ed8886a8..3b420b21 100644
--- a/ui/gtk3/ibusemojidialog.h
+++ b/ui/gtk3/ibusemojidialog.h
@@ -1,7 +1,7 @@
 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
 /* vim:set et sts=4: */
 /* bus - The Input Bus
- * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2017 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -196,5 +196,12 @@ void          ibus_emojier_set_partial_match_length
  */
 void          ibus_emojier_set_partial_match_condition
                                                   (gint         condition);
+/**
+ * ibus_emojier_load_unicode_dict:
+ *
+ * Load the dictionary of #IBusUnicodeData.
+ */
+void          ibus_emojier_load_unicode_dict      (void);
+
 G_END_DECLS
 #endif
diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
index 4f032f6f..bcb3ed75 100644
--- a/ui/gtk3/panel.vala
+++ b/ui/gtk3/panel.vala
@@ -3,7 +3,7 @@
  * ibus - The Input Bus
  *
  * Copyright(c) 2011-2014 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright(c) 2015-2017 Takao Fujwiara <takao.fujiwara1@gmail.com>
+ * Copyright(c) 2015-2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -871,6 +871,7 @@ class Panel : IBus.PanelService {
             IBusEmojier.set_annotation_lang(
                     m_settings_emoji.get_string("lang"));
             m_emojier_set_emoji_lang_id = 0;
+            IBusEmojier.load_unicode_dict();
             return false;
         });
     }
-- 
2.14.3

From 4cfd5ad7c6d071cfef6c7d678cc027ea480b8fc9 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 6 Feb 2018 11:02:09 +0900
Subject: [PATCH] Fix typo in ibusunicode.c

Review URL: https://codereview.appspot.com/340740043
---
 src/ibusunicode.c    | 5 +++--
 ui/gtk3/emojier.vala | 4 ----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/ibusunicode.c b/src/ibusunicode.c
index 8559819d..aac9c135 100644
--- a/src/ibusunicode.c
+++ b/src/ibusunicode.c
@@ -412,7 +412,8 @@ ibus_unicode_data_list_deserialize (GVariant *variant,
                 IBUS_UNICODE_DESERIALIZE_SIGNALL_STR,
                 G_OBJECT_TYPE (source_object));
         if (!has_signal) {
-            const gchar type_name = g_type_name (source_object);
+            const gchar *type_name =
+                    g_type_name (G_OBJECT_TYPE (source_object));
             g_warning ("GObject %s does not have the signal \"%s\"",
                        type_name ? type_name : "(null)",
                        IBUS_UNICODE_DESERIALIZE_SIGNALL_STR);
@@ -677,7 +678,7 @@ ibus_unicode_block_class_init (IBusUnicodeBlockClass *class)
     GObjectClass *gobject_class = G_OBJECT_CLASS (class);
     IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
 
-    object_class->destroy = (IBusObjectDestroyFunc) ibus_unicode_data_destroy;
+    object_class->destroy = (IBusObjectDestroyFunc) ibus_unicode_block_destroy;
     gobject_class->set_property =
             (GObjectSetPropertyFunc) ibus_unicode_block_set_property;
     gobject_class->get_property =
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 555ea68f..0bf34da8 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -1373,8 +1373,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
                     return;
                 }
             }
-            // TODO: Provide a custom description and annotation for
-            // the favorite emojis.
             EPaddedLabelBox widget = new EPaddedLabelBox(
                         _("Description: %s").printf(_("None")),
                         Gtk.Align.START);
@@ -1790,8 +1788,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
          * prevention logic:
          * https://mail.gnome.org/archives/gtk-devel-list/2017-May/msg00026.html
          */
-        //uint32 timestamp = event.get_time();
-        //present_with_time(timestamp);
         present_centralize(event);
 
         Gdk.Device pointer;
-- 
2.14.3

From fb07f64764f18f702221ff5574b2fd2193f051f0 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 20 Feb 2018 17:25:07 +0900
Subject: [PATCH] Implement ibus-extension-gtk3 for the global keybinding

Currently IBus panel (ibus-ui-gtk3) is not available in GNOME and Plasma
so the emoji and unicode point typings are not available in GNOME and Plasma.
The workaround `ibus emoji` command is available but it put the selected
character into the copy buffer and users have to paste the character.

Originaly the emoji feature was implemented in IBus GtkIMModule but
it had several problems; the first is the keybinding is hard-coded
and IBus GtkIMModule does not use GSettings for the customized settings.
The second is the feature was available for GTK applications.
The third is that XKB input sources uses gtk-im-context-simple
but not ibus in GNOME desktop so users have to add an IM input sources
to enable IBus for the XKB input sources. The fourth is the feature
was available for IBusEngineSimple only and other IBus IMEs need to
inherit that class to get the emoji feature. The fifth is that
emoji typing is available for English only since IBusEngineSimple
had the feature. The sixth is that the default one dimension lookup
window was not useful to choose an emoji and needed two dimensions
lookup window.

And the implementation was moved from IBus GtkIMModule to IBus panel
to fix above problems.
But users have to use `ibus emoji` at present if ibus-ui-gtk3
is not available.

Now I think to move the emoji feature from ibus-ui-gtk3 to another
IBus component; ibus-extension-gtk3 which manages the Ctrl-Shift-e.
GNOME and Plasma desktops still do not show the GUI menu but
the shortcut key is available in this implementation.

BUG=RHBZ#1430501
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/339300043
---
 bindings/vala/IBus-1.0-custom.vala |    4 +
 bus/Makefile.am                    |    7 +-
 bus/ibusimpl.c                     |   82 ++-
 bus/main.c                         |   25 +-
 bus/marshalers.list                |    1 +
 bus/panelproxy.c                   |   57 +-
 bus/panelproxy.h                   |   20 +-
 src/Makefile.am                    |    4 +-
 src/ibus.h                         |    1 +
 src/ibusmarshalers.list            |    1 +
 src/ibuspanelservice.c             |   81 ++-
 src/ibuspanelservice.h             |   15 +-
 src/ibusserializable.c             |    7 +-
 src/ibusserializable.h             |   18 +-
 src/ibusshare.c                    |    4 +-
 src/ibusshare.h                    |   17 +-
 src/ibusxevent.c                   | 1004 ++++++++++++++++++++++++++++++++++++
 src/ibusxevent.h                   |  294 +++++++++++
 src/tests/runtest                  |    1 +
 ui/gtk3/Makefile.am                |   59 ++-
 ui/gtk3/bindingcommon.vala         |  215 ++++++++
 ui/gtk3/candidatearea.vala         |  102 ----
 ui/gtk3/extension.vala             |  124 +++++
 ui/gtk3/gtkextension.xml.in        |   12 +
 ui/gtk3/iconwidget.vala            |  103 ++++
 ui/gtk3/panel.vala                 |  408 +++------------
 ui/gtk3/panelbinding.vala          |  335 ++++++++++++
 27 files changed, 2506 insertions(+), 495 deletions(-)
 create mode 100644 src/ibusxevent.c
 create mode 100644 src/ibusxevent.h
 create mode 100644 ui/gtk3/bindingcommon.vala
 create mode 100644 ui/gtk3/extension.vala
 create mode 100644 ui/gtk3/gtkextension.xml.in
 create mode 100644 ui/gtk3/panelbinding.vala

diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala
index 144d75e2..cf1fc3fa 100644
--- a/bindings/vala/IBus-1.0-custom.vala
+++ b/bindings/vala/IBus-1.0-custom.vala
@@ -6,4 +6,8 @@ namespace IBus {
 		[CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)]
 		public Text.from_static_string (string str);
 	}
+	public class XEvent : IBus.Serializable {
+		[CCode (cname = "ibus_x_event_new", has_construct_function = true)]
+		public XEvent (string first_property_name, ...);
+	}
 }
diff --git a/bus/Makefile.am b/bus/Makefile.am
index 864ba923..8bcc8e16 100644
--- a/bus/Makefile.am
+++ b/bus/Makefile.am
@@ -3,7 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2013 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2007-2013 Red Hat, Inc.
+# Copyright (c) 2013-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-2018 Red Hat, Inc.
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -105,6 +106,10 @@ marshalers.c: marshalers.h marshalers.list
 	$(GLIB_GENMARSHAL) --prefix=bus_marshal $(srcdir)/marshalers.list --body --internal) > $@.tmp && \
 	mv $@.tmp $@
 
+if ENABLE_EMOJI_DICT
+AM_CFLAGS += -DEMOJI_DICT
+endif
+
 
 if ENABLE_TESTS
 TESTS = \
diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
index f99307ad..58d205cf 100644
--- a/bus/ibusimpl.c
+++ b/bus/ibusimpl.c
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2011-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -73,6 +74,7 @@ struct _BusIBusImpl {
 
     BusInputContext *focused_context;
     BusPanelProxy   *panel;
+    BusPanelProxy   *extension;
 
     /* a default keymap of ibus-daemon (usually "us") which is used only
      * when use_sys_layout is FALSE. */
@@ -290,12 +292,37 @@ _panel_destroy_cb (BusPanelProxy *panel,
     g_assert (BUS_IS_PANEL_PROXY (panel));
     g_assert (BUS_IS_IBUS_IMPL (ibus));
 
-    g_return_if_fail (ibus->panel == panel);
-
-    ibus->panel = NULL;
+    if (ibus->panel == panel)
+        ibus->panel = NULL;
+    else if (ibus->extension == panel)
+        ibus->extension = NULL;
+    else
+        g_return_if_reached ();
     g_object_unref (panel);
 }
 
+static void
+_panel_panel_extension_cb (BusPanelProxy *panel,
+                           GVariant      *parameters,
+                           BusIBusImpl  *ibus)
+{
+    if (!ibus->extension) {
+        g_warning ("Panel extension is not running.");
+        return;
+    }
+
+    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
+    g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->extension));
+
+    /* Use the DBus method because it seems any DBus signal,
+     * g_dbus_message_new_signal(), cannot be reached to the server. */
+    g_dbus_proxy_call (G_DBUS_PROXY (ibus->extension),
+                       "PanelExtensionReceived",
+                       parameters,
+                       G_DBUS_CALL_FLAGS_NONE,
+                       -1, NULL, NULL, NULL);
+}
+
 static void
 _registry_changed_cb (IBusRegistry *registry,
                       BusIBusImpl  *ibus)
@@ -317,33 +344,47 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
                              const gchar   *new_name,
                              BusIBusImpl   *ibus)
 {
+    PanelType panel_type = PANEL_TYPE_NONE;
+
     g_assert (BUS_IS_DBUS_IMPL (dbus));
     g_assert (name != NULL);
     g_assert (old_name != NULL);
     g_assert (new_name != NULL);
     g_assert (BUS_IS_IBUS_IMPL (ibus));
 
-    if (g_strcmp0 (name, IBUS_SERVICE_PANEL) == 0) {
+    if (!g_strcmp0 (name, IBUS_SERVICE_PANEL))
+        panel_type = PANEL_TYPE_PANEL;
+    else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION))
+        panel_type = PANEL_TYPE_EXTENSION;
+
+    if (panel_type != PANEL_TYPE_NONE) {
         if (g_strcmp0 (new_name, "") != 0) {
             /* a Panel process is started. */
             BusConnection *connection;
             BusInputContext *context = NULL;
-
-            if (ibus->panel != NULL) {
-                ibus_proxy_destroy ((IBusProxy *) ibus->panel);
-                /* panel should be NULL after destroy. See _panel_destroy_cb for details. */
-                g_assert (ibus->panel == NULL);
+            BusPanelProxy   **panel = (panel_type == PANEL_TYPE_PANEL) ?
+                                       &ibus->panel : &ibus->extension;
+
+            if (*panel != NULL) {
+                ibus_proxy_destroy ((IBusProxy *)(*panel));
+                /* panel should be NULL after destroy. See _panel_destroy_cb
+                 * for details. */
+                g_assert (*panel == NULL);
             }
 
             connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
             g_return_if_fail (connection != NULL);
 
-            ibus->panel = bus_panel_proxy_new (connection);
+            *panel = bus_panel_proxy_new (connection, panel_type);
 
-            g_signal_connect (ibus->panel,
+            g_signal_connect (*panel,
                               "destroy",
                               G_CALLBACK (_panel_destroy_cb),
                               ibus);
+            g_signal_connect (*panel,
+                              "panel-extension",
+                              G_CALLBACK (_panel_panel_extension_cb),
+                              ibus);
 
             if (ibus->focused_context != NULL) {
                 context = ibus->focused_context;
@@ -355,14 +396,13 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
             if (context != NULL) {
                 BusEngineProxy *engine;
 
-                bus_panel_proxy_focus_in (ibus->panel, context);
+                bus_panel_proxy_focus_in (*panel, context);
 
                 engine = bus_input_context_get_engine (context);
                 if (engine != NULL) {
                     IBusPropList *prop_list =
                         bus_engine_proxy_get_properties (engine);
-                    bus_panel_proxy_register_properties (ibus->panel,
-                                                         prop_list);
+                    bus_panel_proxy_register_properties (*panel, prop_list);
                 }
             }
         }
@@ -403,6 +443,7 @@ bus_ibus_impl_init (BusIBusImpl *ibus)
     ibus->contexts = NULL;
     ibus->focused_context = NULL;
     ibus->panel = NULL;
+    ibus->extension = NULL;
 
     ibus->keymap = ibus_keymap_get ("us");
 
@@ -635,6 +676,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
 
         if (ibus->panel != NULL)
             bus_panel_proxy_focus_out (ibus->panel, ibus->focused_context);
+        if (ibus->extension != NULL)
+            bus_panel_proxy_focus_out (ibus->extension, ibus->focused_context);
 
         bus_input_context_get_content_type (ibus->focused_context,
                                             &purpose, &hints);
@@ -658,6 +701,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
 
         if (ibus->panel != NULL)
             bus_panel_proxy_focus_in (ibus->panel, context);
+        if (ibus->extension != NULL)
+            bus_panel_proxy_focus_in (ibus->extension, context);
     }
 
     if (engine != NULL)
@@ -846,8 +891,13 @@ _context_destroy_cb (BusInputContext    *context,
         bus_ibus_impl_set_focused_context (ibus, NULL);
 
     if (ibus->panel &&
-        bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS)
+        bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
         bus_panel_proxy_destroy_context (ibus->panel, context);
+    }
+    if (ibus->extension &&
+        bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
+        bus_panel_proxy_destroy_context (ibus->extension, context);
+    }
 
     ibus->contexts = g_list_remove (ibus->contexts, context);
     g_object_unref (context);
diff --git a/bus/main.c b/bus/main.c
index 6ad60179..5b2589b1 100644
--- a/bus/main.c
+++ b/bus/main.c
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2013-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -42,6 +43,7 @@ static gboolean xim = FALSE;
 static gboolean replace = FALSE;
 static gboolean restart = FALSE;
 static gchar *panel = "default";
+static gchar *panel_extension = "default";
 static gchar *config = "default";
 static gchar *desktop = "gnome";
 
@@ -60,6 +62,7 @@ static const GOptionEntry entries[] =
     { "xim",       'x', 0, G_OPTION_ARG_NONE,   &xim,       "execute ibus XIM server.", NULL },
     { "desktop",   'n', 0, G_OPTION_ARG_STRING, &desktop,   "specify the name of desktop session. [default=gnome]", "name" },
     { "panel",     'p', 0, G_OPTION_ARG_STRING, &panel,     "specify the cmdline of panel program. pass 'disable' not to start a panel program.", "cmdline" },
+    { "panel-extension", 'E', 0, G_OPTION_ARG_STRING, &panel_extension, "specify the cmdline of panel extension program. pass 'disable' not to start an extension program.", "cmdline" },
     { "config",    'c', 0, G_OPTION_ARG_STRING, &config,    "specify the cmdline of config program. pass 'disable' not to start a config program.", "cmdline" },
     { "address",   'a', 0, G_OPTION_ARG_STRING, &g_address,   "specify the address of ibus daemon.", "address" },
     { "replace",   'r', 0, G_OPTION_ARG_NONE,   &replace,   "if there is an old ibus-daemon is running, it will be replaced.", NULL },
@@ -268,7 +271,27 @@ main (gint argc, gchar **argv)
             if (!execute_cmdline (panel))
                 exit (-1);
         }
+
+#ifdef EMOJI_DICT
+        if (g_strcmp0 (panel_extension, "default") == 0) {
+            BusComponent *component;
+            component = bus_ibus_impl_lookup_component_by_name (
+                    BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION);
+            if (component) {
+                bus_component_set_restart (component, restart);
+            }
+            if (component == NULL ||
+                !bus_component_start (component, g_verbose)) {
+                g_printerr ("Can not execute default panel program\n");
+                exit (-1);
+            }
+        } else if (g_strcmp0 (panel_extension, "disable") != 0 &&
+                   g_strcmp0 (panel_extension, "") != 0) {
+            if (!execute_cmdline (panel_extension))
+                exit (-1);
+        }
     }
+#endif
 
     /* execute ibus xim server */
     if (xim) {
diff --git a/bus/marshalers.list b/bus/marshalers.list
index c032cdaa..437c6fee 100644
--- a/bus/marshalers.list
+++ b/bus/marshalers.list
@@ -12,4 +12,5 @@ VOID:STRING
 VOID:STRING,INT
 VOID:UINT,UINT
 VOID:UINT,UINT,UINT
+VOID:VARIANT
 VOID:VOID
diff --git a/bus/panelproxy.c b/bus/panelproxy.c
index 8381d7dc..c3908fcf 100644
--- a/bus/panelproxy.c
+++ b/bus/panelproxy.c
@@ -2,8 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2014 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
- * Copyright (C) 2008-2014 Red Hat, Inc.
+ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -51,6 +51,7 @@ enum {
     PROPERTY_SHOW,
     PROPERTY_HIDE,
     COMMIT_TEXT,
+    PANEL_EXTENSION,
     LAST_SIGNAL,
 };
 
@@ -59,6 +60,7 @@ struct _BusPanelProxy {
 
     /* instance members */
     BusInputContext *focused_context;
+    PanelType panel_type;
 };
 
 struct _BusPanelProxyClass {
@@ -110,22 +112,39 @@ static void     bus_panel_proxy_commit_text
 G_DEFINE_TYPE(BusPanelProxy, bus_panel_proxy, IBUS_TYPE_PROXY)
 
 BusPanelProxy *
-bus_panel_proxy_new (BusConnection *connection)
+bus_panel_proxy_new (BusConnection *connection,
+                     PanelType      panel_type)
 {
+    const gchar *path = NULL;
+    GObject *obj;
+    BusPanelProxy *panel;
+
     g_assert (BUS_IS_CONNECTION (connection));
 
-    GObject *obj;
+    switch (panel_type) {
+    case PANEL_TYPE_PANEL:
+        path = IBUS_PATH_PANEL;
+        break;
+    case PANEL_TYPE_EXTENSION:
+        path = IBUS_PATH_PANEL_EXTENSION;
+        break;
+    default:
+        g_return_val_if_reached (NULL);
+    }
+
     obj = g_initable_new (BUS_TYPE_PANEL_PROXY,
                           NULL,
                           NULL,
-                          "g-object-path",     IBUS_PATH_PANEL,
+                          "g-object-path",     path,
                           "g-interface-name",  IBUS_INTERFACE_PANEL,
                           "g-connection",      bus_connection_get_dbus_connection (connection),
                           "g-default-timeout", g_gdbus_timeout,
                           "g-flags",           G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
                           NULL);
 
-    return BUS_PANEL_PROXY (obj);
+    panel = BUS_PANEL_PROXY (obj);
+    panel->panel_type = panel_type;
+    return panel;
 }
 
 static void
@@ -231,6 +250,16 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class)
             bus_marshal_VOID__OBJECT,
             G_TYPE_NONE, 1,
             IBUS_TYPE_TEXT);
+
+    panel_signals[PANEL_EXTENSION] =
+        g_signal_new (I_("panel-extension"),
+            G_TYPE_FROM_CLASS (class),
+            G_SIGNAL_RUN_LAST,
+            0,
+            NULL, NULL,
+            bus_marshal_VOID__VARIANT,
+            G_TYPE_NONE, 1,
+            G_TYPE_VARIANT);
 }
 
 static void
@@ -337,6 +366,15 @@ bus_panel_proxy_g_signal (GDBusProxy  *proxy,
         return;
     }
 
+    if (g_strcmp0 ("PanelExtension", signal_name) == 0) {
+        if (panel->panel_type != PANEL_TYPE_PANEL) {
+            g_warning ("Wrong signal");
+            return;
+        }
+        g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, parameters);
+        return;
+    }
+
     /* shound not be reached */
     g_return_if_reached ();
 }
@@ -832,3 +870,10 @@ bus_panel_proxy_destroy_context (BusPanelProxy    *panel,
 
     g_object_unref (context);
 }
+
+PanelType
+bus_panel_proxy_get_panel_type (BusPanelProxy    *panel)
+{
+    g_assert (BUS_IS_PANEL_PROXY (panel));
+    return panel->panel_type;
+}
diff --git a/bus/panelproxy.h b/bus/panelproxy.h
index 5002f86d..b5a7af17 100644
--- a/bus/panelproxy.h
+++ b/bus/panelproxy.h
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2014 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2014 Red Hat, Inc.
+ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -50,12 +51,25 @@
 
 G_BEGIN_DECLS
 
+typedef enum
+{
+    PANEL_TYPE_NONE,
+    PANEL_TYPE_PANEL,
+    PANEL_TYPE_EXTENSION
+} PanelType;
+
 typedef struct _BusPanelProxy BusPanelProxy;
 typedef struct _BusPanelProxyClass BusPanelProxyClass;
 
 GType            bus_panel_proxy_get_type      (void);
-BusPanelProxy   *bus_panel_proxy_new           (BusConnection     *connection);
+BusPanelProxy   *bus_panel_proxy_new           (BusConnection     *connection,
+                                                PanelType          panel_type);
 
+gboolean         bus_panel_proxy_send_signal   (BusPanelProxy   *panel,
+                                                const gchar     *interface_name,
+                                                const gchar     *signal_name,
+                                                GVariant        *parameters,
+                                                GError         **error);
 /* functions that invoke D-Bus methods of the panel component. */
 void             bus_panel_proxy_focus_in      (BusPanelProxy     *panel,
                                                 BusInputContext   *context);
@@ -119,6 +133,8 @@ void             bus_panel_proxy_set_content_type
                                                (BusPanelProxy     *panel,
                                                 guint              purpose,
                                                 guint              hints);
+PanelType        bus_panel_proxy_get_panel_type
+                                               (BusPanelProxy     *panel);
 G_END_DECLS
 #endif
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 1ba418d8..72ec05ab 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,7 +3,7 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
 # Copyright (c) 2007-2017 Red Hat, Inc.
 #
 # This library is free software; you can redistribute it and/or
@@ -103,6 +103,7 @@ ibus_sources =              \
     ibustext.c              \
     ibusunicode.c           \
     ibusutil.c              \
+    ibusxevent.c            \
     ibusxml.c               \
     $(NULL)
 libibus_1_0_la_SOURCES =    \
@@ -155,6 +156,7 @@ ibus_headers =              \
     ibustypes.h             \
     ibusunicode.h           \
     ibusutil.h              \
+    ibusxevent.h            \
     ibusxml.h               \
     $(NULL)
 ibusincludedir = $(includedir)/ibus-@IBUS_API_VERSION@
diff --git a/src/ibus.h b/src/ibus.h
index 8011729f..b15dded9 100644
--- a/src/ibus.h
+++ b/src/ibus.h
@@ -59,6 +59,7 @@
 #include <ibusregistry.h>
 #include <ibusemoji.h>
 #include <ibusunicode.h>
+#include <ibusxevent.h>
 
 #ifndef IBUS_DISABLE_DEPRECATED
 #include <ibuskeysyms-compat.h>
diff --git a/src/ibusmarshalers.list b/src/ibusmarshalers.list
index 918bc7f7..8d91937e 100644
--- a/src/ibusmarshalers.list
+++ b/src/ibusmarshalers.list
@@ -24,4 +24,5 @@ VOID:STRING,STRING,STRING
 VOID:UINT
 VOID:UINT,POINTER
 VOID:POINTER,UINT
+VOID:VARIANT
 OBJECT:STRING
diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c
index 33949fa1..f37b91c3 100644
--- a/src/ibuspanelservice.c
+++ b/src/ibuspanelservice.c
@@ -25,6 +25,10 @@
 #include "ibusmarshalers.h"
 #include "ibusinternal.h"
 
+#define IBUS_PANEL_SERVICE_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_PANEL_SERVICE, \
+                                 IBusPanelServicePrivate))
+
 enum {
     UPDATE_PREEDIT_TEXT,
     UPDATE_AUXILIARY_TEXT,
@@ -52,6 +56,7 @@ enum {
     STATE_CHANGED,
     DESTROY_CONTEXT,
     SET_CONTENT_TYPE,
+    PANEL_EXTENSION_RECEIVED,
     LAST_SIGNAL,
 };
 
@@ -146,6 +151,9 @@ static void      ibus_panel_service_set_content_type
                                    (IBusPanelService       *panel,
                                     guint                   purpose,
                                     guint                   hints);
+static void      ibus_panel_service_panel_extension_received
+                                   (IBusPanelService       *panel,
+                                    GVariant               *data);
 
 G_DEFINE_TYPE (IBusPanelService, ibus_panel_service, IBUS_TYPE_SERVICE)
 
@@ -212,6 +220,9 @@ static const gchar introspection_xml[] =
     "      <arg direction='in'  type='u' name='purpose' />"
     "      <arg direction='in'  type='u' name='hints' />"
     "    </method>"
+    "    <method name='PanelExtensionReceived'>"
+    "      <arg direction='in' type='v' name='data' />"
+    "    </method>"
     /* Signals */
     "    <signal name='CursorUp' />"
     "    <signal name='CursorDown' />"
@@ -235,6 +246,9 @@ static const gchar introspection_xml[] =
     "    <signal name='CommitText'>"
     "      <arg type='v' name='text' />"
     "    </signal>"
+    "    <signal name='PanelExtension'>"
+    "      <arg type='v' name='data' />"
+    "    </signal>"
     "  </interface>"
     "</node>";
 
@@ -274,6 +288,8 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
     class->update_preedit_text   = ibus_panel_service_update_preedit_text;
     class->update_property       = ibus_panel_service_update_property;
     class->set_content_type      = ibus_panel_service_set_content_type;
+    class->panel_extension_received =
+            ibus_panel_service_panel_extension_received;
 
     class->cursor_down_lookup_table = ibus_panel_service_not_implemented;
     class->cursor_up_lookup_table   = ibus_panel_service_not_implemented;
@@ -891,6 +907,30 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
             2,
             G_TYPE_UINT,
             G_TYPE_UINT);
+
+    /**
+     * IBusPanelService::panel-extension-received:
+     * @panel: An #IBusPanelService
+     * @data: A #GVariant
+     *
+     * Emitted when the client application get the ::panel-extension-received.
+     * Implement the member function
+     * IBusPanelServiceClass::panel_extension_received in extended class to
+     * receive this signal.
+     *
+     * <note><para>Argument @user_data is ignored in this function.</para>
+     * </note>
+     */
+    panel_signals[PANEL_EXTENSION_RECEIVED] =
+        g_signal_new (I_("panel-extension-received"),
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_LAST,
+            G_STRUCT_OFFSET (IBusPanelServiceClass, panel_extension_received),
+            NULL, NULL,
+            _ibus_marshal_VOID__VARIANT,
+            G_TYPE_NONE,
+            1,
+            G_TYPE_VARIANT);
 }
 
 static void
@@ -1088,6 +1128,24 @@ ibus_panel_service_service_method_call (IBusService           *service,
         return;
     }
 
+    if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) {
+        GVariant *variant = NULL;
+        g_variant_get (parameters, "(v)", &variant);
+        if (variant == NULL) {
+            g_dbus_method_invocation_return_error (
+                    invocation,
+                    G_DBUS_ERROR,
+                    G_DBUS_ERROR_FAILED,
+                    "PanelExtensionReceived method gives NULL");
+            return;
+        }
+        g_signal_emit (panel, panel_signals[PANEL_EXTENSION_RECEIVED], 0,
+                       variant);
+        g_variant_unref (variant);
+        g_dbus_method_invocation_return_value (invocation, NULL);
+        return;
+    }
+
     const static struct {
         const gchar *name;
         const gint signal_id;
@@ -1259,6 +1317,13 @@ ibus_panel_service_set_content_type (IBusPanelService *panel,
     ibus_panel_service_not_implemented(panel);
 }
 
+static void
+ibus_panel_service_panel_extension_received (IBusPanelService *panel,
+                                             GVariant         *data)
+{
+    ibus_panel_service_not_implemented(panel);
+}
+
 IBusPanelService *
 ibus_panel_service_new (GDBusConnection *connection)
 {
@@ -1347,6 +1412,21 @@ ibus_panel_service_commit_text (IBusPanelService *panel,
     }
 }
 
+void
+ibus_panel_service_panel_extension (IBusPanelService *panel,
+                                    GVariant         *variant)
+{
+    g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
+    g_return_if_fail (variant);
+
+    ibus_service_emit_signal ((IBusService *) panel,
+                              NULL,
+                              IBUS_INTERFACE_PANEL,
+                              "PanelExtension",
+                              g_variant_new ("(v)", variant),
+                              NULL);
+}
+
 #define DEFINE_FUNC(name, Name)                             \
     void                                                    \
     ibus_panel_service_##name (IBusPanelService *panel)     \
@@ -1364,4 +1444,3 @@ DEFINE_FUNC (cursor_up, CursorUp)
 DEFINE_FUNC (page_down, PageDown)
 DEFINE_FUNC (page_up, PageUp)
 #undef DEFINE_FUNC
-
diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h
index a5b13c73..60ef842b 100644
--- a/src/ibuspanelservice.h
+++ b/src/ibuspanelservice.h
@@ -2,7 +2,7 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (c) 2009-2014 Google Inc. All rights reserved.
- * Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -128,6 +128,9 @@ struct _IBusPanelServiceClass {
                                             gint                    y,
                                             gint                    w,
                                             gint                    h);
+    void     (* panel_extension_received)
+                                           (IBusPanelService       *panel,
+                                            GVariant               *data);
 
     /*< private >*/
     /* padding */
@@ -242,5 +245,15 @@ void ibus_panel_service_property_hide     (IBusPanelService *panel,
 void ibus_panel_service_commit_text       (IBusPanelService *panel,
                                            IBusText         *text);
 
+/**
+ * ibus_panel_service_panel_extension:
+ * @panel: An #IBusPanelService
+ * @data: (transfer full): A #GVariant data which is sent to a panel extension. 
+ *
+ * Notify that a data is sent
+ * by sending a "PanelExtension" message to IBus panel extension service.
+ */
+void ibus_panel_service_panel_extension   (IBusPanelService *panel,
+                                           GVariant         *data);
 G_END_DECLS
 #endif
diff --git a/src/ibusserializable.c b/src/ibusserializable.c
index d7f867f4..a377b613 100644
--- a/src/ibusserializable.c
+++ b/src/ibusserializable.c
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2010 Red Hat, Inc.
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -251,7 +252,7 @@ ibus_serializable_copy (IBusSerializable *object)
 }
 
 GVariant *
-ibus_serializable_serialize (IBusSerializable *object)
+ibus_serializable_serialize_object (IBusSerializable *object)
 {
     g_return_val_if_fail (IBUS_IS_SERIALIZABLE (object), FALSE);
     gboolean retval;
@@ -267,7 +268,7 @@ ibus_serializable_serialize (IBusSerializable *object)
 }
 
 IBusSerializable *
-ibus_serializable_deserialize (GVariant *variant)
+ibus_serializable_deserialize_object (GVariant *variant)
 {
     g_return_val_if_fail (variant != NULL, NULL);
 
diff --git a/src/ibusserializable.h b/src/ibusserializable.h
index 4327eaee..102de1bd 100644
--- a/src/ibusserializable.h
+++ b/src/ibusserializable.h
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -248,10 +249,10 @@ void                 ibus_serializable_remove_qattachment
  *
  * See also: IBusSerializableCopyFunc().
  */
-IBusSerializable    *ibus_serializable_copy             (IBusSerializable   *serializable);
+IBusSerializable    *ibus_serializable_copy (IBusSerializable   *serializable);
 
 /**
- * ibus_serializable_serialize:
+ * ibus_serializable_serialize_object:
  * @serializable: An #IBusSerializable.
  *
  * Serialize an #IBusSerializable to a #GVariant.
@@ -261,10 +262,11 @@ IBusSerializable    *ibus_serializable_copy             (IBusSerializable   *ser
  *
  * See also: IBusSerializableCopyFunc().
  */
-GVariant            *ibus_serializable_serialize        (IBusSerializable   *serializable);
+GVariant            *ibus_serializable_serialize_object
+                                            (IBusSerializable   *serializable);
 
 /**
- * ibus_serializable_deserialize:
+ * ibus_serializable_deserialize_object:
  * @variant: A #GVariant.
  *
  * Deserialize a #GVariant to an #IBusSerializable/
@@ -274,7 +276,11 @@ GVariant            *ibus_serializable_serialize        (IBusSerializable   *ser
  *
  * See also: IBusSerializableCopyFunc().
  */
-IBusSerializable    *ibus_serializable_deserialize      (GVariant           *variant);
+IBusSerializable    *ibus_serializable_deserialize_object
+                                            (GVariant           *variant);
+
+#define ibus_serializable_serialize ibus_serializable_serialize_object
+#define ibus_serializable_deserialize ibus_serializable_deserialize_object
 
 G_END_DECLS
 #endif
diff --git a/src/ibusshare.c b/src/ibusshare.c
index b793a962..d7724a6b 100644
--- a/src/ibusshare.c
+++ b/src/ibusshare.c
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2010 Red Hat, Inc.
+ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -287,6 +288,7 @@ ibus_init (void)
     IBUS_TYPE_ENGINE_DESC;
     IBUS_TYPE_OBSERVED_PATH;
     IBUS_TYPE_REGISTRY;
+    IBUS_TYPE_X_EVENT;
 }
 
 static GMainLoop *main_loop = NULL;
diff --git a/src/ibusshare.h b/src/ibusshare.h
index f3e2011e..757d915b 100644
--- a/src/ibusshare.h
+++ b/src/ibusshare.h
@@ -2,7 +2,8 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2008-2013 Red Hat, Inc.
+ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2018 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -65,6 +66,13 @@
  */
 #define IBUS_SERVICE_PANEL      "org.freedesktop.IBus.Panel"
 
+/**
+ * IBUS_SERVICE_PANEL_EXTENSION:
+ *
+ * Address of IBus panel extension service.
+ */
+#define IBUS_SERVICE_PANEL_EXTENSION "org.freedesktop.IBus.Panel.Extension"
+
 /**
  * IBUS_SERVICE_CONFIG:
  *
@@ -100,6 +108,13 @@
  */
 #define IBUS_PATH_PANEL         "/org/freedesktop/IBus/Panel"
 
+/**
+ * IBUS_PATH_PANEL_EXTENSION:
+ *
+ * D-Bus path for IBus panel.
+ */
+#define IBUS_PATH_PANEL_EXTENSION "/org/freedesktop/IBus/Panel/Extension"
+
 /**
  * IBUS_PATH_CONFIG:
  *
diff --git a/src/ibusxevent.c b/src/ibusxevent.c
new file mode 100644
index 00000000..dea80272
--- /dev/null
+++ b/src/ibusxevent.c
@@ -0,0 +1,1004 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+#include "ibusinternal.h"
+#include "ibusxevent.h"
+
+#define IBUS_X_EVENT_VERSION 1
+#define IBUS_X_EVENT_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_X_EVENT, IBusXEventPrivate))
+
+enum {
+    PROP_0,
+    PROP_VERSION,
+    PROP_EVENT_TYPE,
+    PROP_WINDOW,
+    PROP_SEND_EVENT,
+    PROP_SERIAL,
+    PROP_TIME,
+    PROP_STATE,
+    PROP_KEYVAL,
+    PROP_LENGTH,
+    PROP_STRING,
+    PROP_HARDWARE_KEYCODE,
+    PROP_GROUP,
+    PROP_IS_MODIFIER,
+    PROP_ROOT,
+    PROP_SUBWINDOW,
+    PROP_X,
+    PROP_Y,
+    PROP_X_ROOT,
+    PROP_Y_ROOT,
+    PROP_SAME_SCREEN,
+    PROP_PURPOSE
+};
+
+
+struct _IBusXEventPrivate {
+    guint    version;
+    guint32  time;
+    guint    state;
+    guint    keyval;
+    gint     length;
+    gchar   *string;
+    guint16  hardware_keycode;
+    guint8   group;
+    gboolean is_modifier;
+    guint    root;
+    guint    subwindow;
+    gint     x;
+    gint     y;
+    gint     x_root;
+    gint     y_root;
+    gboolean same_screen;
+    gchar   *purpose;
+};
+
+/* functions prototype */
+static void      ibus_x_event_destroy        (IBusXEvent         *event);
+static void      ibus_x_event_set_property   (IBusXEvent         *event,
+                                              guint               prop_id,
+                                              const GValue       *value,
+                                              GParamSpec         *pspec);
+static void      ibus_x_event_get_property   (IBusXEvent         *event,
+                                              guint               prop_id,
+                                              GValue             *value,
+                                              GParamSpec         *pspec);
+static gboolean  ibus_x_event_serialize      (IBusXEvent         *event,
+                                              GVariantBuilder    *builder);
+static gint      ibus_x_event_deserialize    (IBusXEvent         *event,
+                                              GVariant           *variant);
+static gboolean  ibus_x_event_copy           (IBusXEvent         *dest,
+                                              const IBusXEvent   *src);
+
+G_DEFINE_TYPE (IBusXEvent, ibus_x_event, IBUS_TYPE_SERIALIZABLE)
+
+static void
+ibus_x_event_class_init (IBusXEventClass *class)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+    IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
+    IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
+
+    gobject_class->set_property =
+            (GObjectSetPropertyFunc) ibus_x_event_set_property;
+    gobject_class->get_property =
+            (GObjectGetPropertyFunc) ibus_x_event_get_property;
+
+    object_class->destroy = (IBusObjectDestroyFunc) ibus_x_event_destroy;
+
+    serializable_class->serialize   =
+            (IBusSerializableSerializeFunc) ibus_x_event_serialize;
+    serializable_class->deserialize =
+            (IBusSerializableDeserializeFunc) ibus_x_event_deserialize;
+    serializable_class->copy        =
+            (IBusSerializableCopyFunc) ibus_x_event_copy;
+
+    /* install properties */
+    /**
+     * IBusXEvent:version:
+     *
+     * Version of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_VERSION,
+                    g_param_spec_uint ("version",
+                        "version",
+                        "version",
+                        0,
+                        G_MAXUINT32,
+                        IBUS_X_EVENT_VERSION,
+                        G_PARAM_READABLE));
+
+    /**
+     * IBusXEvent:event-type:
+     *
+     * IBusXEventType of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_EVENT_TYPE,
+                    g_param_spec_int ("event-type",
+                        "event type",
+                        "event type",
+                        -1,
+                        G_MAXINT32,
+                        -1,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:window:
+     *
+     * window of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_WINDOW,
+                    g_param_spec_uint ("window",
+                        "window",
+                        "window",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:send-event:
+     *
+     * send_event of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_SEND_EVENT,
+                    g_param_spec_int ("send-event",
+                        "send event",
+                        "send event",
+                        0,
+                        G_MAXINT8,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:serial:
+     *
+     * serial of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_SERIAL,
+                    g_param_spec_ulong ("serial",
+                        "serial",
+                        "serial",
+                        0,
+                        G_MAXUINT64,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:time:
+     *
+     * time of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_TIME,
+                    g_param_spec_uint ("time",
+                        "time",
+                        "time",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:state:
+     *
+     * state of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_STATE,
+                    g_param_spec_uint ("state",
+                        "state",
+                        "state",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:keyval:
+     *
+     * keyval of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_KEYVAL,
+                    g_param_spec_uint ("keyval",
+                        "keyval",
+                        "keyval",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:length:
+     *
+     * keyval of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_LENGTH,
+                    g_param_spec_int ("length",
+                        "length",
+                        "length",
+                        -1,
+                        G_MAXINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:string:
+     *
+     * string of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_STRING,
+                    g_param_spec_string ("string",
+                        "string",
+                        "string",
+                        "",
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:hardware-keycode:
+     *
+     * hardware keycode of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_HARDWARE_KEYCODE,
+                    g_param_spec_uint ("hardware-keycode",
+                        "hardware keycode",
+                        "hardware keycode",
+                        0,
+                        G_MAXUINT16,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:group:
+     *
+     * group of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_GROUP,
+                    g_param_spec_uint ("group",
+                        "group",
+                        "group",
+                        0,
+                        G_MAXUINT8,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:is-modifier:
+     *
+     * is_modifier of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_IS_MODIFIER,
+                    g_param_spec_boolean ("is-modifier",
+                        "is modifier",
+                        "is modifier",
+                        FALSE,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:root:
+     *
+     * root window of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_ROOT,
+                    g_param_spec_uint ("root",
+                        "root",
+                        "root",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:subwindow:
+     *
+     * subwindow of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_SUBWINDOW,
+                    g_param_spec_uint ("subwindow",
+                        "subwindow",
+                        "subwindow",
+                        0,
+                        G_MAXUINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:x:
+     *
+     * x of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_X,
+                    g_param_spec_int ("x",
+                        "x",
+                        "x",
+                        G_MININT32,
+                        G_MAXINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:y:
+     *
+     * x of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_Y,
+                    g_param_spec_int ("y",
+                        "y",
+                        "y",
+                        G_MININT32,
+                        G_MAXINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:x-root:
+     *
+     * root-x of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_X_ROOT,
+                    g_param_spec_int ("x-root",
+                        "x root",
+                        "x root",
+                        G_MININT32,
+                        G_MAXINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:y-root:
+     *
+     * root-y of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_Y_ROOT,
+                    g_param_spec_int ("y-root",
+                        "y root",
+                        "y root",
+                        G_MININT32,
+                        G_MAXINT32,
+                        0,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:same-screen:
+     *
+     * same_screen of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_SAME_SCREEN,
+                    g_param_spec_boolean ("same-screen",
+                        "same screen",
+                        "same screen",
+                        TRUE,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusXEvent:purpose:
+     *
+     * purpose of this IBusXEvent.
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_PURPOSE,
+                    g_param_spec_string ("purpose",
+                        "purpose",
+                        "purpose",
+                        "",
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+    g_type_class_add_private (class, sizeof (IBusXEventPrivate));
+}
+
+static void
+ibus_x_event_init (IBusXEvent *event)
+{
+    event->priv = IBUS_X_EVENT_GET_PRIVATE (event);
+    event->priv->version = IBUS_X_EVENT_VERSION;
+}
+
+static void
+ibus_x_event_destroy (IBusXEvent *event)
+{
+    g_clear_pointer (&event->priv->string, g_free);
+
+    IBUS_OBJECT_CLASS(ibus_x_event_parent_class)->destroy (IBUS_OBJECT (event));
+}
+
+static void
+ibus_x_event_set_property (IBusXEvent   *event,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+    IBusXEventPrivate *priv = event->priv;
+
+    switch (prop_id) {
+    case PROP_EVENT_TYPE:
+        event->event_type = g_value_get_int (value);
+        break;
+    case PROP_WINDOW:
+        event->window = g_value_get_uint (value);
+        break;
+    case PROP_SEND_EVENT:
+        event->send_event = g_value_get_int (value);
+        break;
+    case PROP_SERIAL:
+        event->serial = g_value_get_ulong (value);
+        break;
+    case PROP_TIME:
+        priv->time = g_value_get_uint (value);
+        break;
+    case PROP_STATE:
+        priv->state = g_value_get_uint (value);
+        break;
+    case PROP_KEYVAL:
+        priv->keyval = g_value_get_uint (value);
+        break;
+    case PROP_LENGTH:
+        priv->length = g_value_get_int (value);
+        break;
+    case PROP_STRING:
+        g_free (priv->string);
+        priv->string = g_value_dup_string (value);
+        break;
+    case PROP_HARDWARE_KEYCODE:
+        priv->hardware_keycode = g_value_get_uint (value);
+        break;
+    case PROP_GROUP:
+        priv->group = g_value_get_uint (value);
+        break;
+    case PROP_IS_MODIFIER:
+        priv->is_modifier = g_value_get_boolean (value);
+        break;
+    case PROP_ROOT:
+        priv->root = g_value_get_uint (value);
+        break;
+    case PROP_SUBWINDOW:
+        priv->subwindow = g_value_get_uint (value);
+        break;
+    case PROP_X:
+        priv->x = g_value_get_int (value);
+        break;
+    case PROP_Y:
+        priv->y = g_value_get_int (value);
+        break;
+    case PROP_X_ROOT:
+        priv->x_root = g_value_get_int (value);
+        break;
+    case PROP_Y_ROOT:
+        priv->y_root = g_value_get_int (value);
+        break;
+    case PROP_SAME_SCREEN:
+        priv->same_screen = g_value_get_boolean (value);
+        break;
+    case PROP_PURPOSE:
+        g_free (priv->purpose);
+        priv->purpose = g_value_dup_string (value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
+    }
+}
+
+static void
+ibus_x_event_get_property (IBusXEvent *event,
+                          guint        prop_id,
+                          GValue      *value,
+                          GParamSpec  *pspec)
+{
+    IBusXEventPrivate *priv = event->priv;
+    switch (prop_id) {
+    case PROP_VERSION:
+        g_value_set_uint (value, priv->version);
+        break;
+    case PROP_EVENT_TYPE:
+        g_value_set_int (value, event->event_type);
+        break;
+    case PROP_WINDOW:
+        g_value_set_uint (value, event->window);
+        break;
+    case PROP_SEND_EVENT:
+        g_value_set_int (value, event->send_event);
+        break;
+    case PROP_SERIAL:
+        g_value_set_ulong (value, event->serial);
+        break;
+    case PROP_TIME:
+        g_value_set_uint (value, priv->time);
+        break;
+    case PROP_STATE:
+        g_value_set_uint (value, priv->state);
+        break;
+    case PROP_KEYVAL:
+        g_value_set_uint (value, priv->keyval);
+        break;
+    case PROP_LENGTH:
+        g_value_set_int (value, priv->length);
+        break;
+    case PROP_STRING:
+        g_value_set_string (value, priv->string);
+        break;
+    case PROP_HARDWARE_KEYCODE:
+        g_value_set_uint (value, priv->hardware_keycode);
+        break;
+    case PROP_GROUP:
+        g_value_set_uint (value, priv->group);
+        break;
+    case PROP_IS_MODIFIER:
+        g_value_set_boolean (value, priv->is_modifier);
+        break;
+    case PROP_ROOT:
+        g_value_set_uint (value, priv->root);
+        break;
+    case PROP_SUBWINDOW:
+        g_value_set_uint (value, priv->subwindow);
+        break;
+    case PROP_X:
+        g_value_set_int (value, priv->x);
+        break;
+    case PROP_Y:
+        g_value_set_int (value, priv->y);
+        break;
+    case PROP_X_ROOT:
+        g_value_set_int (value, priv->x_root);
+        break;
+    case PROP_Y_ROOT:
+        g_value_set_int (value, priv->y_root);
+        break;
+    case PROP_SAME_SCREEN:
+        g_value_set_boolean (value, priv->same_screen);
+        break;
+    case PROP_PURPOSE:
+        g_value_set_string (value, priv->purpose);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
+    }
+}
+
+static gboolean
+ibus_x_event_serialize (IBusXEvent      *event,
+                        GVariantBuilder *builder)
+{
+    gboolean retval;
+    IBusXEventPrivate *priv;
+
+    retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
+            serialize ((IBusSerializable *)event, builder);
+    g_return_val_if_fail (retval, FALSE);
+    /* End dict iter */
+
+    priv = event->priv;
+#define NOTNULL(s) ((s) != NULL ? (s) : "")
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_builder_add (builder, "u", priv->version);
+    g_variant_builder_add (builder, "u", event->event_type);
+    g_variant_builder_add (builder, "u", event->window);
+    g_variant_builder_add (builder, "i", event->send_event);
+    g_variant_builder_add (builder, "t", event->serial);
+    g_variant_builder_add (builder, "u", priv->time);
+    g_variant_builder_add (builder, "u", priv->state);
+    g_variant_builder_add (builder, "u", priv->keyval);
+    g_variant_builder_add (builder, "i", priv->length);
+    g_variant_builder_add (builder, "s", NOTNULL (priv->string));
+    g_variant_builder_add (builder, "u", priv->hardware_keycode);
+    g_variant_builder_add (builder, "u", priv->group);
+    g_variant_builder_add (builder, "b", priv->is_modifier);
+    g_variant_builder_add (builder, "u", priv->root);
+    g_variant_builder_add (builder, "u", priv->subwindow);
+    g_variant_builder_add (builder, "i", priv->x);
+    g_variant_builder_add (builder, "i", priv->y);
+    g_variant_builder_add (builder, "i", priv->x_root);
+    g_variant_builder_add (builder, "i", priv->y_root);
+    g_variant_builder_add (builder, "b", priv->same_screen);
+    g_variant_builder_add (builder, "s", NOTNULL (priv->purpose));
+#undef NOTNULL
+
+    return TRUE;
+}
+
+static gint
+ibus_x_event_deserialize (IBusXEvent *event,
+                          GVariant   *variant)
+{
+    gint retval;
+    IBusXEventPrivate *priv;
+
+    retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
+            deserialize ((IBusSerializable *)event, variant);
+    g_return_val_if_fail (retval, 0);
+
+    priv = event->priv;
+    /* If you will add a new property, you can append it at the end and
+     * you should not change the serialized order of name, longname,
+     * description, ... because the order is also used in other applications
+     * likes ibus-qt. */
+    g_variant_get_child (variant, retval++, "u", &priv->version);
+    g_variant_get_child (variant, retval++, "u", &event->event_type);
+    g_variant_get_child (variant, retval++, "u", &event->window);
+    g_variant_get_child (variant, retval++, "i", &event->send_event);
+    g_variant_get_child (variant, retval++, "t", &event->serial);
+    g_variant_get_child (variant, retval++, "u", &priv->time);
+    g_variant_get_child (variant, retval++, "u", &priv->state);
+    g_variant_get_child (variant, retval++, "u", &priv->keyval);
+    g_variant_get_child (variant, retval++, "i", &priv->length);
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &priv->string);
+    g_variant_get_child (variant, retval++, "u", &priv->hardware_keycode);
+    g_variant_get_child (variant, retval++, "u", &priv->group);
+    g_variant_get_child (variant, retval++, "b", &priv->is_modifier);
+    g_variant_get_child (variant, retval++, "u", &priv->root);
+    g_variant_get_child (variant, retval++, "u", &priv->subwindow);
+    g_variant_get_child (variant, retval++, "i", &priv->x);
+    g_variant_get_child (variant, retval++, "i", &priv->y);
+    g_variant_get_child (variant, retval++, "i", &priv->x_root);
+    g_variant_get_child (variant, retval++, "i", &priv->y_root);
+    g_variant_get_child (variant, retval++, "b", &priv->same_screen);
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &priv->purpose);
+
+    return retval;
+}
+
+static gboolean
+ibus_x_event_copy (IBusXEvent       *dest,
+                   const IBusXEvent *src)
+{
+    gboolean retval;
+    IBusXEventPrivate *dest_priv = dest->priv;
+    IBusXEventPrivate *src_priv = src->priv;
+
+    retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
+            copy ((IBusSerializable *)dest, (IBusSerializable *)src);
+    g_return_val_if_fail (retval, FALSE);
+
+    dest_priv->version           = src_priv->version;
+    dest->event_type             = src->event_type;
+    dest->window                 = src->window;
+    dest->send_event             = src->send_event;
+    dest->serial                 = src->serial;
+    dest_priv->time              = src_priv->time;
+    dest_priv->state             = src_priv->state;
+    dest_priv->keyval            = src_priv->keyval;
+    dest_priv->length            = src_priv->length;
+    dest_priv->string            = g_strdup (src_priv->string);
+    dest_priv->hardware_keycode  = src_priv->hardware_keycode;
+    dest_priv->group             = src_priv->group;
+    dest_priv->is_modifier       = src_priv->is_modifier;
+    dest_priv->root              = src_priv->root;
+    dest_priv->subwindow         = src_priv->subwindow;
+    dest_priv->x                 = src_priv->x;
+    dest_priv->y                 = src_priv->y;
+    dest_priv->x_root            = src_priv->x_root;
+    dest_priv->y_root            = src_priv->y_root;
+    dest_priv->same_screen       = src_priv->same_screen;
+    dest_priv->purpose           = g_strdup (src_priv->purpose);
+
+    return TRUE;
+}
+
+IBusXEvent *
+ibus_x_event_new (const gchar   *first_property_name,
+                  ...)
+{
+    va_list var_args;
+    IBusXEvent *event;
+
+    va_start (var_args, first_property_name);
+    event = (IBusXEvent *) g_object_new_valist (IBUS_TYPE_X_EVENT,
+                                                first_property_name,
+                                                var_args);
+    va_end (var_args);
+    g_assert (event->priv->version != 0);
+    g_assert (event->event_type != IBUS_X_EVENT_NOTHING);
+    return event;
+}
+
+guint
+ibus_x_event_get_version (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    return event->priv->version;
+}
+
+IBusXEventType
+ibus_x_event_get_event_type (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    return event->event_type;
+}
+
+guint32
+ibus_x_event_get_window (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    return event->window;
+}
+
+gint8
+ibus_x_event_get_send_event (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), -1);
+    return event->send_event;
+}
+
+gulong
+ibus_x_event_get_serial (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    return event->serial;
+}
+
+guint32
+ibus_x_event_get_time (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->time;
+}
+
+guint
+ibus_x_event_get_state (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->state;
+}
+
+guint
+ibus_x_event_get_keyval (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->keyval;
+}
+
+gint
+ibus_x_event_get_length (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), -1);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (-1);
+    }
+    return event->priv->length;
+}
+
+const gchar *
+ibus_x_event_get_string (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), "");
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached ("");
+    }
+    return event->priv->string;
+}
+
+guint16
+ibus_x_event_get_hardware_keycode (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->hardware_keycode;
+}
+
+guint8
+ibus_x_event_get_group (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->group;
+}
+
+gboolean
+ibus_x_event_get_is_modifier (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->is_modifier;
+}
+
+guint32
+ibus_x_event_get_root (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->root;
+}
+
+guint32
+ibus_x_event_get_subwindow (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->subwindow;
+}
+
+gint
+ibus_x_event_get_x (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->x;
+}
+
+gint
+ibus_x_event_get_y (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->y;
+}
+
+gint
+ibus_x_event_get_x_root (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->x_root;
+}
+
+gint
+ibus_x_event_get_y_root (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (0);
+    }
+    return event->priv->y_root;
+}
+
+gboolean
+ibus_x_event_get_same_screen (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), TRUE);
+    switch (event->event_type) {
+    case IBUS_X_EVENT_KEY_PRESS:
+    case IBUS_X_EVENT_KEY_RELEASE:
+        break;
+    default:
+        g_return_val_if_reached (TRUE);
+    }
+    return event->priv->same_screen;
+}
+
+const gchar *
+ibus_x_event_get_purpose (IBusXEvent *event)
+{
+    g_return_val_if_fail (IBUS_IS_X_EVENT (event), "");
+    return event->priv->purpose;
+}
diff --git a/src/ibusxevent.h b/src/ibusxevent.h
new file mode 100644
index 00000000..f35f14e4
--- /dev/null
+++ b/src/ibusxevent.h
@@ -0,0 +1,294 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#if !defined (__IBUS_H_INSIDE__) && !defined (IBUS_COMPILATION)
+#error "Only <ibus.h> can be included directly"
+#endif
+
+#ifndef __IBUS_X_EVENT_H_
+#define __IBUS_X_EVENT_H_
+
+/**
+ * SECTION: ibusxevent
+ * @short_description: XEvent wrapper object
+ * @title: IBusXEvent
+ * @stability: Unstable
+ *
+ * An IBusXEvent provides a wrapper of XEvent.
+ *
+ * see_also: #IBusComponent, #IBusEngineDesc
+ */
+
+#include "ibusserializable.h"
+
+/*
+ * Type macros.
+ */
+
+/* define GOBJECT macros */
+#define IBUS_TYPE_X_EVENT            \
+    (ibus_x_event_get_type ())
+#define IBUS_X_EVENT(obj)            \
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_X_EVENT, IBusXEvent))
+#define IBUS_X_EVENT_CLASS(klass)    \
+    (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_X_EVENT, IBusXEventClass))
+#define IBUS_IS_X_EVENT(obj)         \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_X_EVENT))
+#define IBUS_IS_X_EVENT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_X_EVENT))
+#define IBUS_X_EVENT_GET_CLASS(obj)  \
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_X_EVENT, IBusXEventClass))
+
+G_BEGIN_DECLS
+
+typedef struct _IBusXEvent IBusXEvent;
+typedef struct _IBusXEventClass IBusXEventClass;
+typedef struct _IBusXEventPrivate IBusXEventPrivate;
+
+typedef enum {
+    IBUS_X_EVENT_NOTHING           = -1,
+    IBUS_X_EVENT_KEY_PRESS         = 0,
+    IBUS_X_EVENT_KEY_RELEASE       = 1,
+    IBUS_X_EVENT_OTHER             = 2,
+    IBUS_X_EVENT_EVENT_LAST        /* helper variable for decls */
+} IBusXEventType;
+
+/**
+ * IBusXEvent:
+ * @type: event type
+ *
+ * IBusEngine properties.
+ */
+struct _IBusXEvent {
+    /*< private >*/
+    IBusSerializable parent;
+    IBusXEventPrivate *priv;
+
+    /* instance members */
+    /*< public >*/
+    IBusXEventType event_type;
+    guint          window;
+    gint8          send_event;
+    gulong         serial;
+};
+
+struct _IBusXEventClass {
+    /*< private >*/
+    IBusSerializableClass parent;
+
+    /* class members */
+    /*< public >*/
+
+    /*< private >*/
+    /* padding */
+    gpointer pdummy[10];
+};
+
+GType        ibus_x_event_get_type       (void);
+
+/**
+ * ibus_x_event_new:
+ * @first_property_name: Name of the first property.
+ * @...: the NULL-terminated arguments of the properties and values.
+ *
+ * Create a new #IBusXEvent.
+ *
+ * Returns: A newly allocated #IBusXEvent. E.g.
+ * ibus_x_event_new ("event-type", IBUS_X_EVENT_KEY_PRESS, NULL);
+ */
+IBusXEvent *   ibus_x_event_new            (const gchar
+                                                           *first_property_name,
+                                            ...);
+
+/**
+ * ibus_x_event_get_version:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: Version of #IBusXEvent
+ */
+guint          ibus_x_event_get_version    (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_event_type:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: IBusXEventType of #IBusXEvent
+ */
+IBusXEventType ibus_x_event_get_event_type (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_window:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: XID of #IBusXEvent
+ */
+guint32        ibus_x_event_get_window     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_send_event:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: send_event of #IBusXEvent
+ */
+gint8          ibus_x_event_get_send_event (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_serial:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: serial of #IBusXEvent
+ */
+gulong         ibus_x_event_get_serial     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_time:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: time of #IBusXEvent
+ */
+guint32        ibus_x_event_get_time       (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_state:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: state of #IBusXEvent
+ */
+guint          ibus_x_event_get_state      (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_keyval:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: keyval of #IBusXEvent
+ */
+guint          ibus_x_event_get_keyval     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_length:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: length of #IBusXEvent
+ */
+gint           ibus_x_event_get_length     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_string:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: string of #IBusXEvent
+ */
+const gchar *  ibus_x_event_get_string     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_hardware_keycode:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: hardware keycode of #IBusXEvent
+ */
+guint16        ibus_x_event_get_hardware_keycode
+                                           (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_group:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: group of #IBusXEvent
+ */
+guint8         ibus_x_event_get_group      (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_is_modifier:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: is_modifier of #IBusXEvent
+ */
+gboolean       ibus_x_event_get_is_modifier
+                                           (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_subwindow:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: subwindow of #IBusXEvent
+ */
+guint32        ibus_x_event_get_subwindow  (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_root:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: root window of #IBusXEvent
+ */
+guint32        ibus_x_event_get_root       (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_x:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: x of #IBusXEvent
+ */
+gint           ibus_x_event_get_x          (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_y:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: y of #IBusXEvent
+ */
+gint           ibus_x_event_get_y          (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_x_root:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: x-root of #IBusXEvent
+ */
+gint           ibus_x_event_get_x_root     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_y_root:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: y-root of #IBusXEvent
+ */
+gint           ibus_x_event_get_y_root     (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_same_screen:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: same_screen of #IBusXEvent
+ */
+gboolean       ibus_x_event_get_same_screen
+                                           (IBusXEvent         *event);
+
+/**
+ * ibus_x_event_get_purpose:
+ * @event: An #IBusXEvent.
+ *
+ * Returns: purpose of #IBusXEvent
+ */
+const gchar *  ibus_x_event_get_purpose    (IBusXEvent         *event);
+
+G_END_DECLS
+#endif
diff --git a/src/tests/runtest b/src/tests/runtest
index 91c4e95f..0e43fee5 100755
--- a/src/tests/runtest
+++ b/src/tests/runtest
@@ -106,6 +106,7 @@ test -d $tstdir || mkdir $tstdir
     --daemonize \
     --cache=none \
     --panel=disable \
+    --panel-extension=disable \
     --config=default \
     --verbose;
 
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 786b80e6..0a8f4200 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -3,8 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2015-2017 Takao Fujwiara <takao.fujiwara1@gmail.com>
-# Copyright (c) 2007-2017 Red Hat, Inc.
+# Copyright (c) 2015-2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-2018 Red Hat, Inc.
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -30,7 +30,7 @@ component_DATA = \
 	$(NULL)
 componentdir = $(pkgdatadir)/component
 
-gtkpanel.xml: gtkpanel.xml.in
+%.xml: %.xml.in
 	$(AM_V_GEN) sed \
 		-e 's|@VERSION[@]|$(VERSION)|g' \
 		-e 's|@libexecdir[@]|$(libexecdir)|g' $< > $@.tmp && \
@@ -108,6 +108,7 @@ libexec_PROGRAMS = ibus-ui-gtk3
 
 ibus_ui_gtk3_SOURCES = \
 	application.vala \
+	bindingcommon.vala \
 	candidatearea.vala \
 	candidatepanel.vala \
 	emojier.vala \
@@ -155,9 +156,12 @@ EXTRA_DIST =                            \
     $(emoji_headers)                    \
     $(man_seven_in_files)               \
     emojierapp.vala                     \
+    extension.vala                      \
+    gtkextension.xml.in                 \
     gtkpanel.xml.in                     \
     notification-item.xml               \
     notification-watcher.xml            \
+    panelbinding.vala                   \
     $(NULL)
 
 if ENABLE_EMOJI_DICT
@@ -167,10 +171,8 @@ libexec_PROGRAMS += ibus-ui-emojier
 
 ibus_ui_emojier_VALASOURCES =                   \
     emojierapp.vala                             \
-    candidatearea.vala                          \
     emojier.vala                                \
     iconwidget.vala                             \
-    pango.vala                                  \
     separator.vala                              \
     $(NULL)
 ibus_ui_emojier_SOURCES =                       \
@@ -198,6 +200,53 @@ emojierapp.o: $(srcdir)/emojierapp.c
 	$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
 	$(NULL)
 
+component_DATA += gtkextension.xml
+CLEANFILES += gtkextension.xml
+libexec_PROGRAMS += ibus-extension-gtk3
+
+ibus_extension_gtk3_VALASOURCES =               \
+    bindingcommon.vala                          \
+    emojier.vala                                \
+    extension.vala                              \
+    iconwidget.vala                             \
+    keybindingmanager.vala                      \
+    panelbinding.vala                           \
+    $(NULL)
+ibus_extension_gtk3_SOURCES =                   \
+    $(ibus_extension_gtk3_VALASOURCES:.vala=.c) \
+    $(NULL)
+
+ibus_extension_gtk3_LDADD =                     \
+    $(AM_LDADD)                                 \
+    $(NULL)
+ibus_extension_gtk3_VALAFLAGS =                 \
+    $(AM_VALAFLAGS)                             \
+    $(NULL)
+
+# This line and foo_VALASOURCES line can delete the duplicated entries
+# of emojier.c: emojier.vala
+extension.c: $(ibus_extension_gtk3_VALASOURCES)
+	$(AM_V_VALAC)$(am__cd) $(srcdir) && $(VALAC) $(AM_VALAFLAGS) \
+$(VALAFLAGS) -C $(ibus_extension_gtk3_VALASOURCES)
+	$(NULL)
+# make dist creates .c files in a different srcdir
+extension.o: $(srcdir)/extension.c
+	$(AM_V_CC)source='$<' object='$@' libtool=no \
+	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \
+	$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
+	$(NULL)
+# of emojier.c: emojier.vala
+panelbinding.c: $(ibus_extension_gtk3_VALASOURCES)
+	$(AM_V_VALAC)$(am__cd) $(srcdir) && $(VALAC) $(AM_VALAFLAGS) \
+$(VALAFLAGS) -C $(ibus_extension_gtk3_VALASOURCES)
+	$(NULL)
+# make dist creates .c files in a different srcdir
+panelbinding.o: $(srcdir)/panelbinding.c
+	$(AM_V_CC)source='$<' object='$@' libtool=no \
+	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \
+	$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
+	$(NULL)
+
 man_seven_files = $(man_seven_in_files:.7.in=.7)
 man_seven_DATA =$(man_seven_files:.7=.7.gz)
 man_sevendir = $(mandir)/man7
diff --git a/ui/gtk3/bindingcommon.vala b/ui/gtk3/bindingcommon.vala
new file mode 100644
index 00000000..4171f29d
--- /dev/null
+++ b/ui/gtk3/bindingcommon.vala
@@ -0,0 +1,215 @@
+/* vim:set et sts=4 sw=4:
+ *
+ * ibus - The Input Bus
+ *
+ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
+ * Copyright(c) 2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+/* This file depends on keybindingmanager.vala */
+
+class BindingCommon {
+    public enum KeyEventFuncType {
+        ANY,
+        IME_SWITCHER,
+        EMOJI_TYPING,
+    }
+
+    public class Keybinding : GLib.Object {
+        public Keybinding(uint             keysym,
+                          Gdk.ModifierType modifiers,
+                          bool             reverse,
+                          KeyEventFuncType ftype) {
+            this.keysym = keysym;
+            this.modifiers = modifiers;
+            this.reverse = reverse;
+            this.ftype = ftype;
+        }
+
+        public uint keysym { get; set; }
+        public Gdk.ModifierType modifiers { get; set; }
+        public bool reverse { get; set; }
+        public KeyEventFuncType ftype { get; set; }
+    }
+
+    public delegate void KeybindingFuncHandlerFunc(Gdk.Event event);
+
+    public static void
+    keybinding_manager_bind(KeybindingManager           keybinding_manager,
+                            ref GLib.List<Keybinding>   keybindings,
+                            string?                     accelerator,
+                            KeyEventFuncType            ftype,
+                            KeybindingManager.KeybindingHandlerFunc
+                                                        handler_normal,
+                            KeybindingManager.KeybindingHandlerFunc?
+                                                        handler_reverse) {
+        uint switch_keysym = 0;
+        Gdk.ModifierType switch_modifiers = 0;
+        Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
+        Keybinding keybinding;
+
+        Gtk.accelerator_parse(accelerator,
+                out switch_keysym, out switch_modifiers);
+
+        // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
+        const Gdk.ModifierType VIRTUAL_MODIFIERS = (
+                Gdk.ModifierType.SUPER_MASK |
+                Gdk.ModifierType.HYPER_MASK |
+                Gdk.ModifierType.META_MASK);
+        if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
+        // workaround a bug in gdk vapi vala > 0.18
+        // https://bugzilla.gnome.org/show_bug.cgi?id=677559
+#if VALA_0_18
+            Gdk.Keymap.get_default().map_virtual_modifiers(
+                    ref switch_modifiers);
+#else
+            if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
+                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+            if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
+                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+#endif
+            switch_modifiers &= ~VIRTUAL_MODIFIERS;
+        }
+
+        if (switch_keysym == 0 && switch_modifiers == 0) {
+            warning("Parse accelerator '%s' failed!", accelerator);
+            return;
+        }
+
+        keybinding = new Keybinding(switch_keysym,
+                                    switch_modifiers,
+                                    false,
+                                    ftype);
+        keybindings.append(keybinding);
+
+        keybinding_manager.bind(switch_keysym, switch_modifiers,
+                                handler_normal);
+        if (ftype == KeyEventFuncType.EMOJI_TYPING) {
+            return;
+        }
+
+        // accelerator already has Shift mask
+        if ((switch_modifiers & reverse_modifier) != 0) {
+            return;
+        }
+
+        switch_modifiers |= reverse_modifier;
+
+        keybinding = new Keybinding(switch_keysym,
+                                    switch_modifiers,
+                                    true,
+                                    ftype);
+        keybindings.append(keybinding);
+
+        if (ftype == KeyEventFuncType.IME_SWITCHER) {
+            keybinding_manager.bind(switch_keysym, switch_modifiers,
+                                    handler_reverse);
+        }
+        return;
+    }
+
+    public static void
+    unbind_switch_shortcut(KeyEventFuncType      ftype,
+                           GLib.List<Keybinding> keybindings) {
+        var keybinding_manager = KeybindingManager.get_instance();
+
+        while (keybindings != null) {
+            Keybinding keybinding = keybindings.data;
+
+            if (ftype == KeyEventFuncType.ANY ||
+                ftype == keybinding.ftype) {
+                keybinding_manager.unbind(keybinding.keysym,
+                                          keybinding.modifiers);
+            }
+            keybindings = keybindings.next;
+        }
+    }
+
+    public static void
+    set_custom_font(GLib.Settings?       settings_panel,
+                    GLib.Settings?       settings_emoji,
+                    ref Gtk.CssProvider? css_provider) {
+        Gdk.Display display = Gdk.Display.get_default();
+        Gdk.Screen screen = (display != null) ?
+                display.get_default_screen() : null;
+
+        if (screen == null) {
+            warning("Could not open display.");
+            return;
+        }
+
+        if (settings_emoji != null) {
+            string emoji_font = settings_emoji.get_string("font");
+            if (emoji_font == null) {
+                warning("No config emoji:font.");
+                return;
+            }
+            IBusEmojier.set_emoji_font(emoji_font);
+        }
+
+        if (settings_panel == null)
+            return;
+
+        bool use_custom_font = settings_panel.get_boolean("use-custom-font");
+
+        if (css_provider != null) {
+            Gtk.StyleContext.remove_provider_for_screen(screen,
+                                                        css_provider);
+            css_provider = null;
+        }
+
+        if (use_custom_font == false) {
+            return;
+        }
+
+        string custom_font = settings_panel.get_string("custom-font");
+        if (custom_font == null) {
+            warning("No config panel:custom-font.");
+            return;
+        }
+
+        Pango.FontDescription font_desc =
+                Pango.FontDescription.from_string(custom_font);
+        string font_family = font_desc.get_family();
+        int font_size = font_desc.get_size() / Pango.SCALE;
+        string data;
+
+        if (Gtk.MAJOR_VERSION < 3 ||
+            (Gtk.MAJOR_VERSION == 3 && Gtk.MINOR_VERSION < 20)) {
+            data = "GtkLabel { font: %s; }".printf(custom_font);
+        } else {
+            data = "label { font-family: %s; font-size: %dpt; }"
+                           .printf(font_family, font_size);
+        }
+
+        css_provider = new Gtk.CssProvider();
+
+        try {
+            css_provider.load_from_data(data, -1);
+        } catch (GLib.Error e) {
+            warning("Failed css_provider_from_data: %s: %s", custom_font,
+                                                             e.message);
+            return;
+        }
+
+        Gtk.StyleContext.add_provider_for_screen(
+                screen,
+                css_provider,
+                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+    }
+}
diff --git a/ui/gtk3/candidatearea.vala b/ui/gtk3/candidatearea.vala
index e162a960..f590cf3a 100644
--- a/ui/gtk3/candidatearea.vala
+++ b/ui/gtk3/candidatearea.vala
@@ -21,108 +21,6 @@
  * USA
  */
 
-class ThemedRGBA {
-    public Gdk.RGBA *normal_fg { get; set; }
-    public Gdk.RGBA *normal_bg { get; set; }
-    public Gdk.RGBA *selected_fg { get; set; }
-    public Gdk.RGBA *selected_bg { get; set; }
-
-    private Gtk.StyleContext m_style_context;
-
-    public ThemedRGBA(Gtk.Widget widget) {
-        this.normal_fg = null;
-        this.normal_bg = null;
-        this.selected_fg = null;
-        this.selected_bg = null;
-
-        /* Use the color of Gtk.TextView instead of Gtk.Label
-         * because the selected label "color" is not configured
-         * in "Adwaita" theme and the selected label "background-color"
-         * is not configured in "Maia" theme.
-         * https://github.com/ibus/ibus/issues/1871
-         */
-        Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
-        widget_path.append_type(typeof(Gtk.TextView));
-        m_style_context = new Gtk.StyleContext();
-        m_style_context.set_path(widget_path);
-        m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
-
-        /* "-gtk-secondary-caret-color" value is different
-         * if the parent widget is set in "Menta" theme.
-         */
-        m_style_context.set_parent(widget.get_style_context());
-
-        get_rgba();
-
-        m_style_context.changed.connect(() => { get_rgba(); });
-    }
-
-    ~ThemedRGBA() {
-        reset_rgba();
-    }
-
-    private void reset_rgba() {
-        if (this.normal_fg != null) {
-            this.normal_fg.free();
-            this.normal_fg = null;
-        }
-        if (this.normal_bg != null) {
-            this.normal_bg.free();
-            this.normal_bg = null;
-        }
-        if (this.selected_fg != null) {
-            this.selected_fg.free();
-            this.selected_fg = null;
-        }
-        if (this.selected_bg != null) {
-            this.selected_bg.free();
-            this.selected_bg = null;
-        }
-    }
-
-    private void get_rgba() {
-        reset_rgba();
-        Gdk.RGBA *normal_fg = null;
-        Gdk.RGBA *normal_bg = null;
-        Gdk.RGBA *selected_fg = null;
-        Gdk.RGBA *selected_bg = null;
-        m_style_context.get(Gtk.StateFlags.NORMAL,
-                            "color",
-                            out normal_fg);
-        m_style_context.get(Gtk.StateFlags.SELECTED,
-                            "color",
-                            out selected_fg);
-
-        string bg_prop = "background-color";
-        m_style_context.get(Gtk.StateFlags.NORMAL,
-                            bg_prop,
-                            out normal_bg);
-        m_style_context.get(Gtk.StateFlags.SELECTED,
-                            bg_prop,
-                            out selected_bg);
-        if (normal_bg.red   == selected_bg.red &&
-            normal_bg.green == selected_bg.green &&
-            normal_bg.blue  == selected_bg.blue &&
-            normal_bg.alpha == selected_bg.alpha) {
-            normal_bg.free();
-            normal_bg = null;
-            normal_bg.free();
-            normal_bg = null;
-            bg_prop = "-gtk-secondary-caret-color";
-            m_style_context.get(Gtk.StateFlags.NORMAL,
-                                bg_prop,
-                                out normal_bg);
-            m_style_context.get(Gtk.StateFlags.SELECTED,
-                                bg_prop,
-                                out selected_bg);
-        }
-        this.normal_fg   = normal_fg;
-        this.normal_bg   = normal_bg;
-        this.selected_fg = selected_fg;
-        this.selected_bg = selected_bg;
-    }
-}
-
 class CandidateArea : Gtk.Box {
     private bool m_vertical;
     private Gtk.Label[] m_labels;
diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala
new file mode 100644
index 00000000..a170280b
--- /dev/null
+++ b/ui/gtk3/extension.vala
@@ -0,0 +1,124 @@
+/* vim:set et sts=4 sw=4:
+ *
+ * ibus - The Input Bus
+ *
+ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
+ * Copyright(c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+class ExtensionGtk {
+    private IBus.Bus m_bus;
+    private PanelBinding m_panel;
+
+    public ExtensionGtk(string[] argv) {
+        GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE,
+                                 Config.GLIB_LOCALE_DIR);
+        GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
+        IBus.init();
+        Gtk.init(ref argv);
+
+        m_bus = new IBus.Bus();
+
+        m_bus.connected.connect(bus_connected);
+        m_bus.disconnected.connect(bus_disconnected);
+
+        if (m_bus.is_connected()) {
+            init();
+        }
+    }
+
+    private void init() {
+        DBusConnection connection = m_bus.get_connection();
+        connection.signal_subscribe("org.freedesktop.DBus",
+                                    "org.freedesktop.DBus",
+                                    "NameAcquired",
+                                    "/org/freedesktop/DBus",
+                                    IBus.SERVICE_PANEL_EXTENSION,
+                                    DBusSignalFlags.NONE,
+                                    bus_name_acquired_cb);
+        connection.signal_subscribe("org.freedesktop.DBus",
+                                    "org.freedesktop.DBus",
+                                    "NameLost",
+                                    "/org/freedesktop/DBus",
+                                    IBus.SERVICE_PANEL_EXTENSION,
+                                    DBusSignalFlags.NONE,
+                                    bus_name_lost_cb);
+        var flags =
+                IBus.BusNameFlag.ALLOW_REPLACEMENT |
+                IBus.BusNameFlag.REPLACE_EXISTING;
+        m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION, flags);
+    }
+
+    public int run() {
+        Gtk.main();
+        return 0;
+    }
+
+    private void bus_name_acquired_cb(DBusConnection connection,
+                                      string sender_name,
+                                      string object_path,
+                                      string interface_name,
+                                      string signal_name,
+                                      Variant parameters) {
+        debug("signal_name = %s", signal_name);
+        m_panel = new PanelBinding(m_bus);
+        m_panel.load_settings();
+    }
+
+    private void bus_name_lost_cb(DBusConnection connection,
+                                  string sender_name,
+                                  string object_path,
+                                  string interface_name,
+                                  string signal_name,
+                                  Variant parameters) {
+        // "Destroy" dbus method was called before this callback is called.
+        // "Destroy" dbus method -> ibus_service_destroy()
+        // -> g_dbus_connection_unregister_object()
+        // -> g_object_unref(m_panel) will be called later with an idle method,
+        // which was assigned in the arguments of
+        // g_dbus_connection_register_object()
+        debug("signal_name = %s", signal_name);
+
+        // unref m_panel
+        m_panel.disconnect_signals();
+        m_panel = null;
+    }
+
+    private void bus_disconnected(IBus.Bus bus) {
+        debug("connection is lost.");
+        Gtk.main_quit();
+    }
+
+    private void bus_connected(IBus.Bus bus) {
+        init();
+    }
+
+    public static void main(string[] argv) {
+        // https://bugzilla.redhat.com/show_bug.cgi?id=1226465#c20
+        // In /etc/xdg/plasma-workspace/env/gtk3_scrolling.sh
+        // Plasma deskop sets this variable and prevents Super-space,
+        // and Ctrl-Shift-e when ibus-ui-gtk3 runs after the
+        // desktop is launched.
+        GLib.Environment.unset_variable("GDK_CORE_DEVICE_EVENTS");
+        // for Gdk.X11.get_default_xdisplay()
+        Gdk.set_allowed_backends("x11");
+
+        ExtensionGtk extension = new ExtensionGtk(argv);
+        extension.run();
+    }
+}
diff --git a/ui/gtk3/gtkextension.xml.in b/ui/gtk3/gtkextension.xml.in
new file mode 100644
index 00000000..b8157c97
--- /dev/null
+++ b/ui/gtk3/gtkextension.xml.in
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- filename: gtkextension.xml -->
+<component>
+	<name>org.freedesktop.IBus.Panel.Extension</name>
+	<description>Gtk Panel Extension Component</description>
+	<exec>@libexecdir@/ibus-extension-gtk3</exec>
+	<version>@VERSION@</version>
+	<author>Takao Fujiwara &lt;takao.fujiwara1@gmail.com&gt;</author>
+	<license>GPL</license>
+	<homepage>https://github.com/ibus/ibus/wiki</homepage>
+	<textdomain>ibus</textdomain>
+</component>
diff --git a/ui/gtk3/iconwidget.vala b/ui/gtk3/iconwidget.vala
index d322650c..36643c74 100644
--- a/ui/gtk3/iconwidget.vala
+++ b/ui/gtk3/iconwidget.vala
@@ -3,6 +3,7 @@
  * ibus - The Input Bus
  *
  * Copyright(c) 2011-2014 Peng Huang <shawn.p.huang@gmail.com>
+ * Copyright(c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,6 +21,108 @@
  * USA
  */
 
+class ThemedRGBA {
+    public Gdk.RGBA *normal_fg { get; set; }
+    public Gdk.RGBA *normal_bg { get; set; }
+    public Gdk.RGBA *selected_fg { get; set; }
+    public Gdk.RGBA *selected_bg { get; set; }
+
+    private Gtk.StyleContext m_style_context;
+
+    public ThemedRGBA(Gtk.Widget widget) {
+        this.normal_fg = null;
+        this.normal_bg = null;
+        this.selected_fg = null;
+        this.selected_bg = null;
+
+        /* Use the color of Gtk.TextView instead of Gtk.Label
+         * because the selected label "color" is not configured
+         * in "Adwaita" theme and the selected label "background-color"
+         * is not configured in "Maia" theme.
+         * https://github.com/ibus/ibus/issues/1871
+         */
+        Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
+        widget_path.append_type(typeof(Gtk.TextView));
+        m_style_context = new Gtk.StyleContext();
+        m_style_context.set_path(widget_path);
+        m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
+
+        /* "-gtk-secondary-caret-color" value is different
+         * if the parent widget is set in "Menta" theme.
+         */
+        m_style_context.set_parent(widget.get_style_context());
+
+        get_rgba();
+
+        m_style_context.changed.connect(() => { get_rgba(); });
+    }
+
+    ~ThemedRGBA() {
+        reset_rgba();
+    }
+
+    private void reset_rgba() {
+        if (this.normal_fg != null) {
+            this.normal_fg.free();
+            this.normal_fg = null;
+        }
+        if (this.normal_bg != null) {
+            this.normal_bg.free();
+            this.normal_bg = null;
+        }
+        if (this.selected_fg != null) {
+            this.selected_fg.free();
+            this.selected_fg = null;
+        }
+        if (this.selected_bg != null) {
+            this.selected_bg.free();
+            this.selected_bg = null;
+        }
+    }
+
+    private void get_rgba() {
+        reset_rgba();
+        Gdk.RGBA *normal_fg = null;
+        Gdk.RGBA *normal_bg = null;
+        Gdk.RGBA *selected_fg = null;
+        Gdk.RGBA *selected_bg = null;
+        m_style_context.get(Gtk.StateFlags.NORMAL,
+                            "color",
+                            out normal_fg);
+        m_style_context.get(Gtk.StateFlags.SELECTED,
+                            "color",
+                            out selected_fg);
+
+        string bg_prop = "background-color";
+        m_style_context.get(Gtk.StateFlags.NORMAL,
+                            bg_prop,
+                            out normal_bg);
+        m_style_context.get(Gtk.StateFlags.SELECTED,
+                            bg_prop,
+                            out selected_bg);
+        if (normal_bg.red   == selected_bg.red &&
+            normal_bg.green == selected_bg.green &&
+            normal_bg.blue  == selected_bg.blue &&
+            normal_bg.alpha == selected_bg.alpha) {
+            normal_bg.free();
+            normal_bg = null;
+            normal_bg.free();
+            normal_bg = null;
+            bg_prop = "-gtk-secondary-caret-color";
+            m_style_context.get(Gtk.StateFlags.NORMAL,
+                                bg_prop,
+                                out normal_bg);
+            m_style_context.get(Gtk.StateFlags.SELECTED,
+                                bg_prop,
+                                out selected_bg);
+        }
+        this.normal_fg   = normal_fg;
+        this.normal_bg   = normal_bg;
+        this.selected_fg = selected_fg;
+        this.selected_bg = selected_bg;
+    }
+}
+
 class IconWidget: Gtk.Image {
     /**
      * IconWidget:
diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
index bcb3ed75..d9238c89 100644
--- a/ui/gtk3/panel.vala
+++ b/ui/gtk3/panel.vala
@@ -22,39 +22,16 @@
  */
 
 class Panel : IBus.PanelService {
-    private class Keybinding {
-        public Keybinding(uint keysym,
-                          Gdk.ModifierType modifiers,
-                          bool reverse,
-                          KeyEventFuncType ftype) {
-            this.keysym = keysym;
-            this.modifiers = modifiers;
-            this.reverse = reverse;
-            this.ftype = ftype;
-        }
-
-        public uint keysym { get; set; }
-        public Gdk.ModifierType modifiers { get; set; }
-        public bool reverse { get; set; }
-        public KeyEventFuncType ftype { get; set; }
-    }
 
     private enum IconType {
         STATUS_ICON,
         INDICATOR,
     }
 
-    private enum KeyEventFuncType {
-        ANY,
-        IME_SWITCHER,
-        EMOJI_TYPING,
-    }
-
     private IBus.Bus m_bus;
     private GLib.Settings m_settings_general = null;
     private GLib.Settings m_settings_hotkey = null;
     private GLib.Settings m_settings_panel = null;
-    private GLib.Settings m_settings_emoji = null;
     private IconType m_icon_type = IconType.STATUS_ICON;
     private Indicator m_indicator;
 #if INDICATOR
@@ -73,10 +50,6 @@ class Panel : IBus.PanelService {
     private CandidatePanel m_candidate_panel;
     private Switcher m_switcher;
     private uint m_switcher_focus_set_engine_id;
-    private IBusEmojier? m_emojier;
-    private uint m_emojier_set_emoji_lang_id;
-    private uint m_emojier_focus_commit_text_id;
-    private string[] m_emojier_favorites = {};
     private PropertyManager m_property_manager;
     private PropertyPanel m_property_panel;
     private GLib.Pid m_setup_pid = 0;
@@ -108,7 +81,8 @@ class Panel : IBus.PanelService {
     private ulong m_activate_id;
     private ulong m_registered_status_notifier_item_id;
 
-    private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
+    private GLib.List<BindingCommon.Keybinding> m_keybindings =
+            new GLib.List<BindingCommon.Keybinding>();
 
     public Panel(IBus.Bus bus) {
         GLib.assert(bus.is_connected());
@@ -147,8 +121,6 @@ class Panel : IBus.PanelService {
             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
         }
 
-        bind_emoji_shortcut();
-
         m_property_manager = new PropertyManager();
         m_property_manager.property_activate.connect((w, k, s) => {
             property_activate(k, s);
@@ -168,7 +140,9 @@ class Panel : IBus.PanelService {
         if (m_indicator != null)
             m_indicator.unregister_connection();
 #endif
-        unbind_switch_shortcut(KeyEventFuncType.ANY);
+        BindingCommon.unbind_switch_shortcut(
+                BindingCommon.KeyEventFuncType.ANY, m_keybindings);
+        m_keybindings = null;
     }
 
     private void init_settings() {
@@ -176,7 +150,6 @@ class Panel : IBus.PanelService {
         m_settings_hotkey =
                 new GLib.Settings("org.freedesktop.ibus.general.hotkey");
         m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
-        m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji");
 
         m_settings_general.changed["preload-engines"].connect((key) => {
                 update_engines(m_settings_general.get_strv(key),
@@ -205,16 +178,23 @@ class Panel : IBus.PanelService {
         });
 
         m_settings_hotkey.changed["triggers"].connect((key) => {
-                unbind_switch_shortcut(KeyEventFuncType.IME_SWITCHER);
+                BindingCommon.unbind_switch_shortcut(
+                        BindingCommon.KeyEventFuncType.IME_SWITCHER,
+                        m_keybindings);
+                m_keybindings = null;
                 bind_switch_shortcut();
         });
 
         m_settings_panel.changed["custom-font"].connect((key) => {
-                set_custom_font();
+                BindingCommon.set_custom_font(m_settings_panel,
+                                              null,
+                                              ref m_css_provider);
         });
 
         m_settings_panel.changed["use-custom-font"].connect((key) => {
-                set_custom_font();
+                BindingCommon.set_custom_font(m_settings_panel,
+                                              null,
+                                              ref m_css_provider);
         });
 
         m_settings_panel.changed["show-icon-on-systray"].connect((key) => {
@@ -245,39 +225,6 @@ class Panel : IBus.PanelService {
         m_settings_panel.changed["property-icon-delay-time"].connect((key) => {
                 set_property_icon_delay_time();
         });
-
-        m_settings_emoji.changed["hotkey"].connect((key) => {
-                unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING);
-                bind_emoji_shortcut();
-        });
-
-        m_settings_emoji.changed["font"].connect((key) => {
-                set_custom_font();
-        });
-
-        m_settings_emoji.changed["favorites"].connect((key) => {
-                set_emoji_favorites();
-        });
-
-        m_settings_emoji.changed["favorite-annotations"].connect((key) => {
-                set_emoji_favorites();
-        });
-
-        m_settings_emoji.changed["lang"].connect((key) => {
-                set_emoji_lang();
-        });
-
-        m_settings_emoji.changed["has-partial-match"].connect((key) => {
-                set_emoji_partial_match();
-        });
-
-        m_settings_emoji.changed["partial-match-length"].connect((key) => {
-                set_emoji_partial_match();
-        });
-
-        m_settings_emoji.changed["partial-match-condition"].connect((key) => {
-                set_emoji_partial_match();
-        });
     }
 
     private void popup_menu_at_area_window(Gtk.Menu              menu,
@@ -409,120 +356,40 @@ class Panel : IBus.PanelService {
         m_status_icon.set_from_icon_name("ibus-keyboard");
     }
 
-    private void keybinding_manager_bind(KeybindingManager keybinding_manager,
-                                         string?           accelerator,
-                                         KeyEventFuncType  ftype) {
-        uint switch_keysym = 0;
-        Gdk.ModifierType switch_modifiers = 0;
-        Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
-        Keybinding keybinding;
-
-        Gtk.accelerator_parse(accelerator,
-                out switch_keysym, out switch_modifiers);
-
-        // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
-        const Gdk.ModifierType VIRTUAL_MODIFIERS = (
-                Gdk.ModifierType.SUPER_MASK |
-                Gdk.ModifierType.HYPER_MASK |
-                Gdk.ModifierType.META_MASK);
-        if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
-        // workaround a bug in gdk vapi vala > 0.18
-        // https://bugzilla.gnome.org/show_bug.cgi?id=677559
-#if VALA_0_18
-            Gdk.Keymap.get_default().map_virtual_modifiers(
-                    ref switch_modifiers);
-#else
-            if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
-                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
-            if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
-                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
-#endif
-            switch_modifiers &= ~VIRTUAL_MODIFIERS;
-        }
-
-        if (switch_keysym == 0 && switch_modifiers == 0) {
-            warning("Parse accelerator '%s' failed!", accelerator);
-            return;
-        }
-
-        keybinding = new Keybinding(switch_keysym,
-                                    switch_modifiers,
-                                    false,
-                                    ftype);
-        m_keybindings.append(keybinding);
-
-        /* Workaround not to free the pointer of handle_engine_switch() */
-        if (ftype == KeyEventFuncType.IME_SWITCHER) {
-            keybinding_manager.bind(switch_keysym, switch_modifiers,
-                    (e) => handle_engine_switch(e, false));
-        } else if (ftype == KeyEventFuncType.EMOJI_TYPING) {
-            keybinding_manager.bind(switch_keysym, switch_modifiers,
-                    (e) => handle_emoji_typing(e));
-            return;
-        }
-
-        // accelerator already has Shift mask
-        if ((switch_modifiers & reverse_modifier) != 0) {
-            return;
-        }
-
-        switch_modifiers |= reverse_modifier;
-
-        keybinding = new Keybinding(switch_keysym,
-                                    switch_modifiers,
-                                    true,
-                                    ftype);
-        m_keybindings.append(keybinding);
-
-        if (ftype == KeyEventFuncType.IME_SWITCHER) {
-            keybinding_manager.bind(switch_keysym, switch_modifiers,
-                    (e) => handle_engine_switch(e, true));
-        }
-    }
-
     private void bind_switch_shortcut() {
         string[] accelerators = m_settings_hotkey.get_strv("triggers");
 
         var keybinding_manager = KeybindingManager.get_instance();
 
         foreach (var accelerator in accelerators) {
-            keybinding_manager_bind(keybinding_manager,
-                                    accelerator,
-                                    KeyEventFuncType.IME_SWITCHER);
-        }
-    }
-
-    private void bind_emoji_shortcut() {
-#if EMOJI_DICT
-        string[] accelerators = m_settings_emoji.get_strv("hotkey");
-
-        var keybinding_manager = KeybindingManager.get_instance();
-
-        foreach (var accelerator in accelerators) {
-            keybinding_manager_bind(keybinding_manager,
-                                    accelerator,
-                                    KeyEventFuncType.EMOJI_TYPING);
+            BindingCommon.keybinding_manager_bind(
+                    keybinding_manager,
+                    ref m_keybindings,
+                    accelerator,
+                    BindingCommon.KeyEventFuncType.IME_SWITCHER,
+                    handle_engine_switch_normal,
+                    handle_engine_switch_reverse);
         }
-#endif
     }
 
-    private void unbind_switch_shortcut(KeyEventFuncType ftype) {
+/*
+    public static void
+    unbind_switch_shortcut(KeyEventFuncType      ftype,
+                           GLib.List<Keybinding> keybindings) {
         var keybinding_manager = KeybindingManager.get_instance();
 
-        unowned GLib.List<Keybinding> keybindings = m_keybindings;
-
         while (keybindings != null) {
             Keybinding keybinding = keybindings.data;
 
-            if (ftype == KeyEventFuncType.ANY || ftype == keybinding.ftype) {
+            if (ftype == KeyEventFuncType.ANY ||
+                ftype == keybinding.ftype) {
                 keybinding_manager.unbind(keybinding.keysym,
                                           keybinding.modifiers);
             }
             keybindings = keybindings.next;
         }
-
-        m_keybindings = null;
     }
+*/
 
     /**
      * panel_get_engines_from_xkb:
@@ -670,69 +537,6 @@ class Panel : IBus.PanelService {
         m_settings_general.set_strv("preload-engines", names);
     }
 
-    private void set_custom_font() {
-        Gdk.Display display = Gdk.Display.get_default();
-        Gdk.Screen screen = (display != null) ?
-                display.get_default_screen() : null;
-
-        if (screen == null) {
-            warning("Could not open display.");
-            return;
-        }
-
-        string emoji_font = m_settings_emoji.get_string("font");
-        if (emoji_font == null) {
-            warning("No config emoji:font.");
-            return;
-        }
-        IBusEmojier.set_emoji_font(emoji_font);
-
-        bool use_custom_font = m_settings_panel.get_boolean("use-custom-font");
-
-        if (m_css_provider != null) {
-            Gtk.StyleContext.remove_provider_for_screen(screen,
-                                                        m_css_provider);
-            m_css_provider = null;
-        }
-
-        if (use_custom_font == false) {
-            return;
-        }
-
-        string custom_font = m_settings_panel.get_string("custom-font");
-        if (custom_font == null) {
-            warning("No config panel:custom-font.");
-            return;
-        }
-
-        Pango.FontDescription font_desc =
-                Pango.FontDescription.from_string(custom_font);
-        string font_family = font_desc.get_family();
-        int font_size = font_desc.get_size() / Pango.SCALE;
-        string data;
-
-        if (Gtk.MAJOR_VERSION < 3 ||
-            (Gtk.MAJOR_VERSION == 3 && Gtk.MINOR_VERSION < 20)) {
-            data = "GtkLabel { font: %s; }".printf(custom_font);
-        } else {
-            data = "label { font-family: %s; font-size: %dpt; }"
-                           .printf(font_family, font_size);
-        }
-
-        m_css_provider = new Gtk.CssProvider();
-
-        try {
-            m_css_provider.load_from_data(data, -1);
-        } catch (GLib.Error e) {
-            warning("Failed css_provider_from_data: %s: %s", custom_font,
-                                                             e.message);
-            return;
-        }
-
-        Gtk.StyleContext.add_provider_for_screen(screen,
-                                                 m_css_provider,
-                                                 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
-    }
 
     private void set_switcher_delay_time() {
         m_switcher_delay_time =
@@ -855,35 +659,6 @@ class Panel : IBus.PanelService {
                 m_settings_panel.get_int("property-icon-delay-time");
     }
 
-    private void set_emoji_favorites() {
-        m_emojier_favorites = m_settings_emoji.get_strv("favorites");
-        IBusEmojier.set_favorites(
-                m_emojier_favorites,
-                m_settings_emoji.get_strv("favorite-annotations"));
-    }
-
-    private void set_emoji_lang() {
-        if (m_emojier_set_emoji_lang_id > 0) {
-            GLib.Source.remove(m_emojier_set_emoji_lang_id);
-            m_emojier_set_emoji_lang_id = 0;
-        }
-        m_emojier_set_emoji_lang_id = GLib.Idle.add(() => {
-            IBusEmojier.set_annotation_lang(
-                    m_settings_emoji.get_string("lang"));
-            m_emojier_set_emoji_lang_id = 0;
-            IBusEmojier.load_unicode_dict();
-            return false;
-        });
-    }
-
-    private void set_emoji_partial_match() {
-        IBusEmojier.set_partial_match(
-                m_settings_emoji.get_boolean("has-partial-match"));
-        IBusEmojier.set_partial_match_length(
-                m_settings_emoji.get_int("partial-match-length"));
-        IBusEmojier.set_partial_match_condition(
-                m_settings_emoji.get_int("partial-match-condition"));
-    }
 
     private int compare_versions(string version1, string version2) {
         string[] version1_list = version1.split(".");
@@ -985,12 +760,16 @@ class Panel : IBus.PanelService {
         set_use_xmodmap();
         update_engines(m_settings_general.get_strv("preload-engines"),
                        m_settings_general.get_strv("engines-order"));
-        unbind_switch_shortcut(KeyEventFuncType.ANY);
+        BindingCommon.unbind_switch_shortcut(
+                BindingCommon.KeyEventFuncType.ANY,
+                m_keybindings);
+        m_keybindings = null;
         bind_switch_shortcut();
-        bind_emoji_shortcut();
         set_switcher_delay_time();
         set_embed_preedit_text();
-        set_custom_font();
+        BindingCommon.set_custom_font(m_settings_panel,
+                                      null,
+                                      ref m_css_provider);
         set_show_icon_on_systray();
         set_lookup_table_orientation();
         set_show_property_panel();
@@ -998,9 +777,6 @@ class Panel : IBus.PanelService {
         set_follow_input_cursor_when_always_shown_property_panel();
         set_xkb_icon_rgba();
         set_property_icon_delay_time();
-        set_emoji_favorites();
-        set_emoji_lang();
-        set_emoji_partial_match();
     }
 
     /**
@@ -1037,10 +813,6 @@ class Panel : IBus.PanelService {
             GLib.Source.remove(m_preload_engines_id);
             m_preload_engines_id = 0;
         }
-        if (m_emojier_set_emoji_lang_id > 0) {
-            GLib.Source.remove(m_emojier_set_emoji_lang_id);
-            m_emojier_set_emoji_lang_id = 0;
-        }
     }
 
     private void engine_contexts_insert(IBus.EngineDesc engine) {
@@ -1091,7 +863,15 @@ class Panel : IBus.PanelService {
         set_engine(engine);
     }
 
-    private void handle_engine_switch(Gdk.Event event, bool revert) {
+    private void handle_engine_switch_normal(Gdk.Event event) {
+        handle_engine_switch(event, false);
+    }
+
+    private void handle_engine_switch_reverse(Gdk.Event event) {
+        handle_engine_switch(event, true);
+    }
+
+    private void handle_engine_switch(Gdk.Event event, bool reverse) {
         // Do not need switch IME
         if (m_engines.length <= 1)
             return;
@@ -1105,12 +885,12 @@ class Panel : IBus.PanelService {
         bool pressed = KeybindingManager.primary_modifier_still_pressed(
                 event, primary_modifiers);
 
-        if (revert) {
+        if (reverse) {
             modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
         }
 
         if (pressed && m_switcher_delay_time >= 0) {
-            int i = revert ? m_engines.length - 1 : 1;
+            int i = reverse ? m_engines.length - 1 : 1;
 
             /* The flag of m_switcher.is_running avoids the following problem:
              *
@@ -1132,28 +912,11 @@ class Panel : IBus.PanelService {
                 this.switcher_focus_set_engine();
             }
         } else {
-            int i = revert ? m_engines.length - 1 : 1;
+            int i = reverse ? m_engines.length - 1 : 1;
             switch_engine(i);
         }
     }
 
-    private void show_emojier(Gdk.Event event) {
-        m_emojier = new IBusEmojier();
-        string emoji = m_emojier.run(m_real_current_context_path, event);
-        if (emoji == null) {
-            m_emojier = null;
-            return;
-        }
-        this.emojier_focus_commit();
-    }
-
-    private void handle_emoji_typing(Gdk.Event event) {
-        if (m_emojier != null && m_emojier.is_running()) {
-            m_emojier.present_centralize(event);
-            return;
-        }
-        show_emojier(event);
-    }
 
     private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
         string[] names = {};
@@ -1393,7 +1156,18 @@ class Panel : IBus.PanelService {
                     event.key.window = Gdk.get_default_root_window();
                     event.key.window.ref();
                 }
-                handle_emoji_typing(event);
+                IBus.XEvent xevent = new IBus.XEvent(
+                        "event-type", IBus.XEventType.KEY_PRESS,
+                        "window",
+                        (event.key.window as Gdk.X11.Window).get_xid(),
+                        "time", event.key.time,
+                        "purpose", "emoji");
+                /* new GLib.Variant("(sv)", "emoji", xevent.serialize_object())
+                 * will call g_variant_unref() for the child variant by vala.
+                 * I have no idea not to unref the object so integrated
+                 * the purpose to IBus.XEvent above.
+                 */
+                panel_extension(xevent.serialize_object());
             });
             m_sys_menu.append(item);
 #endif
@@ -1557,67 +1331,6 @@ class Panel : IBus.PanelService {
         }
     }
 
-    private bool emojier_focus_commit_real() {
-        if (m_emojier == null)
-            return true;
-        string selected_string = m_emojier.get_selected_string();
-        string prev_context_path = m_emojier.get_input_context_path();
-        if (selected_string != null &&
-            prev_context_path != "" &&
-            prev_context_path == m_current_context_path) {
-            IBus.Text text = new IBus.Text.from_string(selected_string);
-            commit_text(text);
-            m_emojier = null;
-            bool has_favorite = false;
-            foreach (unowned string favorite in m_emojier_favorites) {
-                if (favorite == selected_string) {
-                    has_favorite = true;
-                    break;
-                }
-            }
-            if (!has_favorite) {
-                m_emojier_favorites += selected_string;
-                m_settings_emoji.set_strv("favorites", m_emojier_favorites);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private void emojier_focus_commit() {
-        if (m_emojier == null)
-            return;
-        string selected_string = m_emojier.get_selected_string();
-        string prev_context_path = m_emojier.get_input_context_path();
-        if (selected_string == null &&
-            prev_context_path != "" &&
-            m_emojier.is_running()) {
-            var context = GLib.MainContext.default();
-            if (m_emojier_focus_commit_text_id > 0 &&
-                context.find_source_by_id(m_emojier_focus_commit_text_id)
-                        != null) {
-                GLib.Source.remove(m_emojier_focus_commit_text_id);
-            }
-            m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => {
-                // focus_in is comming before switcher returns
-                emojier_focus_commit_real();
-                m_emojier_focus_commit_text_id = -1;
-                return false;
-            });
-        } else {
-            if (emojier_focus_commit_real()) {
-                var context = GLib.MainContext.default();
-                if (m_emojier_focus_commit_text_id > 0 &&
-                    context.find_source_by_id(m_emojier_focus_commit_text_id)
-                            != null) {
-                    GLib.Source.remove(m_emojier_focus_commit_text_id);
-                }
-                m_emojier_focus_commit_text_id = -1;
-            }
-        }
-    }
-
     public override void focus_in(string input_context_path) {
         m_current_context_path = input_context_path;
 
@@ -1632,7 +1345,6 @@ class Panel : IBus.PanelService {
             m_real_current_context_path = m_current_context_path;
             m_property_panel.focus_in();
             this.switcher_focus_set_engine();
-            this.emojier_focus_commit();
         }
 
         if (m_use_global_engine)
diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
new file mode 100644
index 00000000..50700121
--- /dev/null
+++ b/ui/gtk3/panelbinding.vala
@@ -0,0 +1,335 @@
+/* vim:set et sts=4 sw=4:
+ *
+ * ibus - The Input Bus
+ *
+ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
+ * Copyright(c) 2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
+ *
+ * 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+class PanelBinding : IBus.PanelService {
+    private IBus.Bus m_bus;
+    private GLib.Settings m_settings_panel = null;
+    private GLib.Settings m_settings_emoji = null;
+    private string m_current_context_path = "";
+    private string m_real_current_context_path = "";
+    private IBusEmojier? m_emojier;
+    private uint m_emojier_set_emoji_lang_id;
+    private uint m_emojier_focus_commit_text_id;
+    private string[] m_emojier_favorites = {};
+    private Gtk.CssProvider m_css_provider;
+    private const uint PRELOAD_ENGINES_DELAY_TIME = 30000;
+    private GLib.List<BindingCommon.Keybinding> m_keybindings =
+            new GLib.List<BindingCommon.Keybinding>();
+
+    public PanelBinding(IBus.Bus bus) {
+        GLib.assert(bus.is_connected());
+        // Chain up base class constructor
+        GLib.Object(connection : bus.get_connection(),
+                    object_path : IBus.PATH_PANEL_EXTENSION);
+
+        m_bus = bus;
+
+        init_settings();
+
+        bind_emoji_shortcut();
+    }
+
+
+    ~PanelBinding() {
+        BindingCommon.unbind_switch_shortcut(
+                BindingCommon.KeyEventFuncType.ANY,
+                m_keybindings);
+    }
+
+
+    private void init_settings() {
+        m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
+        m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji");
+
+        m_settings_panel.changed["custom-font"].connect((key) => {
+                BindingCommon.set_custom_font(m_settings_panel,
+                                              m_settings_emoji,
+                                              ref m_css_provider);
+        });
+
+        m_settings_panel.changed["use-custom-font"].connect((key) => {
+                BindingCommon.set_custom_font(m_settings_panel,
+                                              m_settings_emoji,
+                                              ref m_css_provider);
+        });
+
+        m_settings_emoji.changed["hotkey"].connect((key) => {
+                BindingCommon.unbind_switch_shortcut(
+                        BindingCommon.KeyEventFuncType.EMOJI_TYPING,
+                        m_keybindings);
+                bind_emoji_shortcut();
+        });
+
+        m_settings_emoji.changed["font"].connect((key) => {
+                BindingCommon.set_custom_font(m_settings_panel,
+                                              m_settings_emoji,
+                                              ref m_css_provider);
+        });
+
+        m_settings_emoji.changed["favorites"].connect((key) => {
+                set_emoji_favorites();
+        });
+
+        m_settings_emoji.changed["favorite-annotations"].connect((key) => {
+                set_emoji_favorites();
+        });
+
+        m_settings_emoji.changed["lang"].connect((key) => {
+                set_emoji_lang();
+        });
+
+        m_settings_emoji.changed["has-partial-match"].connect((key) => {
+                set_emoji_partial_match();
+        });
+
+        m_settings_emoji.changed["partial-match-length"].connect((key) => {
+                set_emoji_partial_match();
+        });
+
+        m_settings_emoji.changed["partial-match-condition"].connect((key) => {
+                set_emoji_partial_match();
+        });
+    }
+
+
+    private void bind_emoji_shortcut() {
+#if EMOJI_DICT
+        string[] accelerators = m_settings_emoji.get_strv("hotkey");
+
+        var keybinding_manager = KeybindingManager.get_instance();
+
+        foreach (var accelerator in accelerators) {
+            BindingCommon.keybinding_manager_bind(
+                    keybinding_manager,
+                    ref m_keybindings,
+                    accelerator,
+                    BindingCommon.KeyEventFuncType.EMOJI_TYPING,
+                    handle_emoji_typing,
+                    null);
+        }
+#endif
+    }
+
+
+    private void set_emoji_favorites() {
+        m_emojier_favorites = m_settings_emoji.get_strv("favorites");
+        IBusEmojier.set_favorites(
+                m_emojier_favorites,
+                m_settings_emoji.get_strv("favorite-annotations"));
+    }
+
+
+    private void set_emoji_lang() {
+        if (m_emojier_set_emoji_lang_id > 0) {
+            GLib.Source.remove(m_emojier_set_emoji_lang_id);
+            m_emojier_set_emoji_lang_id = 0;
+        }
+        m_emojier_set_emoji_lang_id = GLib.Idle.add(() => {
+            IBusEmojier.set_annotation_lang(
+                    m_settings_emoji.get_string("lang"));
+            m_emojier_set_emoji_lang_id = 0;
+            IBusEmojier.load_unicode_dict();
+            return false;
+        });
+    }
+
+
+    private void set_emoji_partial_match() {
+        IBusEmojier.set_partial_match(
+                m_settings_emoji.get_boolean("has-partial-match"));
+        IBusEmojier.set_partial_match_length(
+                m_settings_emoji.get_int("partial-match-length"));
+        IBusEmojier.set_partial_match_condition(
+                m_settings_emoji.get_int("partial-match-condition"));
+    }
+
+
+    public void load_settings() {
+        BindingCommon.unbind_switch_shortcut(BindingCommon.KeyEventFuncType.ANY,
+                                             m_keybindings);
+        bind_emoji_shortcut();
+        BindingCommon.set_custom_font(m_settings_panel,
+                                      m_settings_emoji,
+                                      ref m_css_provider);
+        set_emoji_favorites();
+        set_emoji_lang();
+        set_emoji_partial_match();
+    }
+
+
+    /**
+     * disconnect_signals:
+     *
+     * Call this API before m_panel = null so that the ref_count becomes 0
+     */
+    public void disconnect_signals() {
+        if (m_emojier_set_emoji_lang_id > 0) {
+            GLib.Source.remove(m_emojier_set_emoji_lang_id);
+            m_emojier_set_emoji_lang_id = 0;
+        }
+    }
+
+
+    private void show_emojier(Gdk.Event event) {
+        m_emojier = new IBusEmojier();
+        string emoji = m_emojier.run(m_real_current_context_path, event);
+        if (emoji == null) {
+            m_emojier = null;
+            return;
+        }
+        this.emojier_focus_commit();
+    }
+
+
+    private void handle_emoji_typing(Gdk.Event event) {
+        if (m_emojier != null && m_emojier.is_running()) {
+            m_emojier.present_centralize(event);
+            return;
+        }
+        show_emojier(event);
+    }
+
+
+    private bool emojier_focus_commit_real() {
+        if (m_emojier == null)
+            return true;
+        string selected_string = m_emojier.get_selected_string();
+        string prev_context_path = m_emojier.get_input_context_path();
+        if (selected_string != null &&
+            prev_context_path != "" &&
+            prev_context_path == m_current_context_path) {
+            IBus.Text text = new IBus.Text.from_string(selected_string);
+            commit_text(text);
+            m_emojier = null;
+            bool has_favorite = false;
+            foreach (unowned string favorite in m_emojier_favorites) {
+                if (favorite == selected_string) {
+                    has_favorite = true;
+                    break;
+                }
+            }
+            if (!has_favorite) {
+                m_emojier_favorites += selected_string;
+                m_settings_emoji.set_strv("favorites", m_emojier_favorites);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+
+    private void emojier_focus_commit() {
+        if (m_emojier == null)
+            return;
+        string selected_string = m_emojier.get_selected_string();
+        string prev_context_path = m_emojier.get_input_context_path();
+        if (selected_string == null &&
+            prev_context_path != "" &&
+            m_emojier.is_running()) {
+            var context = GLib.MainContext.default();
+            if (m_emojier_focus_commit_text_id > 0 &&
+                context.find_source_by_id(m_emojier_focus_commit_text_id)
+                        != null) {
+                GLib.Source.remove(m_emojier_focus_commit_text_id);
+            }
+            m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => {
+                // focus_in is comming before switcher returns
+                emojier_focus_commit_real();
+                m_emojier_focus_commit_text_id = -1;
+                return false;
+            });
+        } else {
+            if (emojier_focus_commit_real()) {
+                var context = GLib.MainContext.default();
+                if (m_emojier_focus_commit_text_id > 0 &&
+                    context.find_source_by_id(m_emojier_focus_commit_text_id)
+                            != null) {
+                    GLib.Source.remove(m_emojier_focus_commit_text_id);
+                }
+                m_emojier_focus_commit_text_id = -1;
+            }
+        }
+    }
+
+
+    public override void focus_in(string input_context_path) {
+        m_current_context_path = input_context_path;
+
+        /* 'fake' input context is named as 
+         * '/org/freedesktop/IBus/InputContext_1' and always send in
+         * focus-out events by ibus-daemon for the global engine mode.
+         * Now ibus-daemon assumes to always use the global engine.
+         * But this event should not be used for modal dialogs
+         * such as Switcher.
+         */
+        if (!input_context_path.has_suffix("InputContext_1")) {
+            m_real_current_context_path = m_current_context_path;
+            this.emojier_focus_commit();
+        }
+    }
+
+
+    public override void focus_out(string input_context_path) {
+        m_current_context_path = "";
+    }
+
+
+    public override void panel_extension_received(GLib.Variant data) {
+        IBus.XEvent? xevent = IBus.Serializable.deserialize_object(data)
+                as IBus.XEvent;
+        if (xevent == null) {
+            warning ("Failed to deserialize IBusXEvent");
+            return;
+        }
+        if (xevent.get_purpose() != "emoji") {
+            string format = "The purpose %s is not implemented in PanelExtension";
+            warning (format.printf(xevent.get_purpose()));
+            return;
+        }
+        Gdk.EventType event_type;
+        if (xevent.get_event_type() == IBus.XEventType.KEY_PRESS) {
+            event_type = Gdk.EventType.KEY_PRESS;
+        } else if (xevent.get_event_type() == IBus.XEventType.KEY_RELEASE) {
+            event_type = Gdk.EventType.KEY_RELEASE;
+        } else {
+            warning ("Not supported type %d".printf(xevent.get_event_type()));
+            return;
+        }
+        Gdk.Event event = new Gdk.Event(event_type);
+        event.key.time = xevent.get_time();
+        Gdk.Display? display = Gdk.Display.get_default();
+        X.Window xid = xevent.get_window();
+        Gdk.X11.Window window;
+        window = Gdk.X11.Window.lookup_for_display(
+                display as Gdk.X11.Display, xid);
+        if (window != null) {
+            event.key.window = window;
+        } else {
+            window = new Gdk.X11.Window.foreign_for_display(
+                    display as Gdk.X11.Display, xid);
+            event.key.window = window;
+        }
+        handle_emoji_typing(event);
+    }
+}
-- 
2.14.3

From 366963d57d1468914611c71929cc64c83be9affd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Dr=C4=85g?= <piotrdrag@gmail.com>
Date: Tue, 20 Feb 2018 18:57:32 +0900
Subject: [PATCH] Fix typos in translatable strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

BUG=https://github.com/ibus/ibus/pull/1983
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/333670043

Patch from Piotr Drąg <piotrdrag@gmail.com>.
---
 data/ibus.schemas.in    | 6 +++---
 ui/gtk3/emojierapp.vala | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
index 278362d7..4523ccc4 100644
--- a/data/ibus.schemas.in
+++ b/data/ibus.schemas.in
@@ -64,7 +64,7 @@
       <default>[ara,bg,cz,dev,gr,gur,in,jp(kana),mal,mkd,ru,ua]</default>
       <locale name="C">
         <short>Latin layouts which have no ASCII</short>
-           <long>US layout is appended to the latin layouts. variant can be
+           <long>US layout is appended to the Latin layouts. variant can be
                  omitted.
            </long>
       </locale>
@@ -299,7 +299,7 @@
                   and blue, 3. a RGB color in form 'rgb(r,g,b)' or
                   4. a RGBA color in form 'rgba(r,g,b,a)' where 'r',
                   'g', and 'b' are either integers in the range 0 to 255
-                  or precentage values in the range 0% to 100%, and
+                  or percentage values in the range 0% to 100%, and
                   'a' is a floating point value in the range 0 to 1
                   of the alpha.</long>
       </locale>
@@ -373,7 +373,7 @@
       <default>Monospace 16</default>
       <locale name="C">
         <short>Custom font</short>
-	    <long>Custom font name for emoji chracters on emoji dialog</long>
+	    <long>Custom font name for emoji characters on emoji dialog</long>
       </locale>
     </schema>
     <schema>
diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
index d816352e..efedf344 100644
--- a/ui/gtk3/emojierapp.vala
+++ b/ui/gtk3/emojierapp.vala
@@ -94,7 +94,7 @@ public class EmojiApplication : Application {
               /* TRANSLATORS: "FONT" should be capital and translatable.
                * It's used for an argument command --font=FONT
                */
-              N_("\"FONT\" for emoji chracters on emoji dialog"),
+              N_("\"FONT\" for emoji characters on emoji dialog"),
               N_("FONT") },
             { "lang", 0, 0, OptionArg.STRING, out annotation_lang,
               /* TRANSLATORS: "LANG" should be capital and translatable.
-- 
2.14.3

From d1ebb3d77ebfe8f58188261c383d8122e2125fe6 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 21 Feb 2018 12:12:11 +0900
Subject: [PATCH] ui/gtk3: Show code points on Unicode name list dialog

The code points are useful since the list has many names.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/340770043
---
 ui/gtk3/emojier.vala | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 0bf34da8..c85dfa86 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -198,7 +198,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
     private class EPaddedLabelBox : Gtk.Box {
         public EPaddedLabelBox(string          text,
                                Gtk.Align       align,
-                               TravelDirection direction=TravelDirection.NONE) {
+                               TravelDirection direction=TravelDirection.NONE,
+                               string?         caption=null) {
             GLib.Object(
                 name : "IBusEmojierPaddedLabelBox",
                 orientation : Gtk.Orientation.HORIZONTAL,
@@ -218,6 +219,11 @@ public class IBusEmojier : Gtk.ApplicationWindow {
             }
             EPaddedLabel label = new EPaddedLabel(text, align);
             pack_start(label, true, true, 0);
+            if (caption != null) {
+                EPaddedLabel label_r = new EPaddedLabel(caption,
+                                                        Gtk.Align.END);
+                pack_end(label_r, true, true, 0);
+            }
         }
     }
     private class ETitleLabelBox : Gtk.HeaderBar {
@@ -979,9 +985,13 @@ public class IBusEmojier : Gtk.ApplicationWindow {
         uint n = 0;
         foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
             string name = block.get_name();
+            string caption = "U+%08X".printf(block.get_start());
             EBoxRow row = new EBoxRow(name);
             EPaddedLabelBox widget =
-                    new EPaddedLabelBox(_(name), Gtk.Align.CENTER);
+                    new EPaddedLabelBox(_(name),
+                                        Gtk.Align.CENTER,
+                                        TravelDirection.NONE,
+                                        caption);
             row.add(widget);
             m_list_box.add(row);
             if (n++ == m_category_active_index) {
-- 
2.14.3

From fc54b0c051c2eb4a2c1f836b1415def00314cfc1 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 21 Feb 2018 12:17:31 +0900
Subject: [PATCH] ui/gtk3: Load Unicode data when open the dialog by
 default

The emoji data requires about 10MB and the Unicode data requires about 15MB.
Now the emoji data is loaded at the time of startup and the Unicode data
is loaded if users open the dialog at the beginning.
The settings can be customized with gsettings command and the keys
of 'load-emoji-at-startup' and 'load-unicode-at-startup' in
'org.freedesktop.ibus.panel.emoji' schema.

Review URL: https://codereview.appspot.com/340780043
---
 data/ibus.schemas.in      | 32 ++++++++++++++++++++++++++++++++
 ui/gtk3/panelbinding.vala | 41 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
index 4523ccc4..3c6b6f69 100644
--- a/data/ibus.schemas.in
+++ b/data/ibus.schemas.in
@@ -455,6 +455,38 @@
                   </long>
       </locale>
     </schema>
+    <schema>
+      <key>/schemas/desktop/ibus/panel/emoji/load-emoji-at-startup</key>
+      <applyto>/desktop/ibus/panel/emoji/load-emoji-at-startup</applyto>
+      <owner>ibus</owner>
+      <type>bool</type>
+      <default>true</default>
+      <locale name="C">
+        <short>Load the emoji data at the time of startup</short>
+	    <long>Load the emoji data at the time of startup if true.
+                  About 10MB memory is needed to load the data.
+                  Load the emoji data when open the emoji dialog at the
+                  beginning if false.
+            </long>
+      </locale>
+    </schema>
+    <schema>
+      <key>/schemas/desktop/ibus/panel/emoji/load-unicode-at-startup</key>
+      <applyto>/desktop/ibus/panel/emoji/load-unicode-at-startup</applyto>
+      <owner>ibus</owner>
+      <type>bool</type>
+      <default>false</default>
+      <locale name="C">
+        <short>Load the Unicode data at the time of startup</short>
+	    <long>Load the Unicode data at the time of startup if true.
+                  About 15MB memory is needed to load the data.
+                  Load the Unicode data when open the emoji dialog at the
+                  beginning if false.
+                  The Unicode data is always loaded after the emoji data
+                  is loaded even if true.
+            </long>
+      </locale>
+    </schema>
     <schema>
       <key>/schemas/desktop/ibus/general/embed_preedit_text</key>
       <applyto>/desktop/ibus/general/embed_preedit_text</applyto>
diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
index 50700121..1fbf6cfc 100644
--- a/ui/gtk3/panelbinding.vala
+++ b/ui/gtk3/panelbinding.vala
@@ -35,6 +35,10 @@ class PanelBinding : IBus.PanelService {
     private const uint PRELOAD_ENGINES_DELAY_TIME = 30000;
     private GLib.List<BindingCommon.Keybinding> m_keybindings =
             new GLib.List<BindingCommon.Keybinding>();
+    private bool m_load_emoji_at_startup;
+    private bool m_loaded_emoji = false;
+    private bool m_load_unicode_at_startup;
+    private bool m_loaded_unicode = false;
 
     public PanelBinding(IBus.Bus bus) {
         GLib.assert(bus.is_connected());
@@ -109,6 +113,14 @@ class PanelBinding : IBus.PanelService {
         m_settings_emoji.changed["partial-match-condition"].connect((key) => {
                 set_emoji_partial_match();
         });
+
+        m_settings_emoji.changed["load-emoji-at-startup"].connect((key) => {
+                set_load_emoji_at_startup();
+        });
+
+        m_settings_emoji.changed["load-unicode-at-startup"].connect((key) => {
+                set_load_unicode_at_startup();
+        });
     }
 
 
@@ -148,7 +160,11 @@ class PanelBinding : IBus.PanelService {
             IBusEmojier.set_annotation_lang(
                     m_settings_emoji.get_string("lang"));
             m_emojier_set_emoji_lang_id = 0;
-            IBusEmojier.load_unicode_dict();
+            m_loaded_emoji = true;
+            if (m_load_unicode_at_startup && !m_loaded_unicode) {
+                IBusEmojier.load_unicode_dict();
+                m_loaded_unicode = true;
+            }
             return false;
         });
     }
@@ -164,7 +180,21 @@ class PanelBinding : IBus.PanelService {
     }
 
 
+    private void set_load_emoji_at_startup() {
+        m_load_emoji_at_startup =
+            m_settings_emoji.get_boolean("load-emoji-at-startup");
+    }
+
+
+    private void set_load_unicode_at_startup() {
+        m_load_unicode_at_startup =
+            m_settings_emoji.get_boolean("load-unicode-at-startup");
+    }
+
     public void load_settings() {
+
+        set_load_emoji_at_startup();
+        set_load_unicode_at_startup();
         BindingCommon.unbind_switch_shortcut(BindingCommon.KeyEventFuncType.ANY,
                                              m_keybindings);
         bind_emoji_shortcut();
@@ -172,7 +202,8 @@ class PanelBinding : IBus.PanelService {
                                       m_settings_emoji,
                                       ref m_css_provider);
         set_emoji_favorites();
-        set_emoji_lang();
+        if (m_load_emoji_at_startup && !m_loaded_emoji)
+            set_emoji_lang();
         set_emoji_partial_match();
     }
 
@@ -191,6 +222,12 @@ class PanelBinding : IBus.PanelService {
 
 
     private void show_emojier(Gdk.Event event) {
+        if (!m_loaded_emoji)
+            set_emoji_lang();
+        if (!m_loaded_unicode && m_loaded_emoji) {
+            IBusEmojier.load_unicode_dict();
+            m_loaded_unicode = true;
+        }
         m_emojier = new IBusEmojier();
         string emoji = m_emojier.run(m_real_current_context_path, event);
         if (emoji == null) {
-- 
2.14.3

From 7ccbd21ca7d163cdd71dc6c0405d8a32d63d324d Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 27 Feb 2018 14:06:30 +0900
Subject: [PATCH 1/2] Disable panel extension for gdm user

The gdm user's process is also running while the login user's process
is running so the double panel extensions are better to be avoided for
the memory usage.

Review URL: https://codereview.appspot.com/340330044
---
 bus/main.c | 49 ++++++++++++++++++++++++++++++-------------------
 1 file changed, 30 insertions(+), 19 deletions(-)

diff --git a/bus/main.c b/bus/main.c
index 5b2589b1..fe603778 100644
--- a/bus/main.c
+++ b/bus/main.c
@@ -47,6 +47,10 @@ static gchar *panel_extension = "default";
 static gchar *config = "default";
 static gchar *desktop = "gnome";
 
+static gchar *panel_extension_disable_users[] = {
+    "gdm"
+};
+
 static void
 show_version_and_quit (void)
 {
@@ -170,6 +174,9 @@ _sig_usr2_handler (int sig)
 gint
 main (gint argc, gchar **argv)
 {
+    int i;
+    const gchar *username = ibus_get_user_name ();
+
     setlocale (LC_ALL, "");
 
     GOptionContext *context = g_option_context_new ("- ibus daemon");
@@ -194,9 +201,7 @@ main (gint argc, gchar **argv)
 
     /* check uid */
     {
-        const gchar *username = ibus_get_user_name ();
-        uid_t uid = getuid ();
-        struct passwd *pwd = getpwuid (uid);
+        struct passwd *pwd = getpwuid (getuid ());
 
         if (pwd == NULL || g_strcmp0 (pwd->pw_name, username) != 0) {
             g_printerr ("Please run ibus-daemon with login user! Do not run ibus-daemon with sudo or su.\n");
@@ -237,6 +242,12 @@ main (gint argc, gchar **argv)
     }
 
     bus_server_init ();
+    for (i = 0; i < G_N_ELEMENTS(panel_extension_disable_users); i++) {
+        if (!g_strcmp0 (username, panel_extension_disable_users[i]) != 0) {
+            panel_extension = "disable";
+            break;
+        }
+    }
     if (!single) {
         /* execute config component */
         if (g_strcmp0 (config, "default") == 0) {
@@ -271,25 +282,25 @@ main (gint argc, gchar **argv)
             if (!execute_cmdline (panel))
                 exit (-1);
         }
+    }
 
 #ifdef EMOJI_DICT
-        if (g_strcmp0 (panel_extension, "default") == 0) {
-            BusComponent *component;
-            component = bus_ibus_impl_lookup_component_by_name (
-                    BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION);
-            if (component) {
-                bus_component_set_restart (component, restart);
-            }
-            if (component == NULL ||
-                !bus_component_start (component, g_verbose)) {
-                g_printerr ("Can not execute default panel program\n");
-                exit (-1);
-            }
-        } else if (g_strcmp0 (panel_extension, "disable") != 0 &&
-                   g_strcmp0 (panel_extension, "") != 0) {
-            if (!execute_cmdline (panel_extension))
-                exit (-1);
+    if (g_strcmp0 (panel_extension, "default") == 0) {
+        BusComponent *component;
+        component = bus_ibus_impl_lookup_component_by_name (
+                BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION);
+        if (component) {
+            bus_component_set_restart (component, restart);
+        }
+        if (component == NULL ||
+            !bus_component_start (component, g_verbose)) {
+            g_printerr ("Can not execute default panel program\n");
+            exit (-1);
         }
+    } else if (g_strcmp0 (panel_extension, "disable") != 0 &&
+               g_strcmp0 (panel_extension, "") != 0) {
+        if (!execute_cmdline (panel_extension))
+            exit (-1);
     }
 #endif
 
-- 
2.14.3

From c57b7c34d75871db172e023b0094b979000f62fc Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 27 Feb 2018 14:10:29 +0900
Subject: [PATCH 2/2] Enable emoji keybinding in Wayland

XI2 keybinding does not work for the root window in Wayland because
of a security issue maybe. Now I think to move the keybinding in
ibus-extension-gtk3 to each IBusEngine.

FIXME: Unfortunatelly gtk_get_current_event_time() cannot get time
for the delayed DBus events and gtk_window_move() does not work for
GtkDialog without a parent window in Wayland.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/333700043
---
 bus/engineproxy.c         |  19 +-
 bus/ibusimpl.c            |  47 +++-
 bus/inputcontext.c        |  29 ++-
 src/Makefile.am           |   2 +
 src/ibus.h                |   1 +
 src/ibusaccelgroup.c      | 529 ++++++++++++++++++++++++++++++++++++++++++++++
 src/ibusaccelgroup.h      |  51 +++++
 src/ibusengine.c          | 179 +++++++++++++++-
 ui/gtk3/emojier.vala      |   1 +
 ui/gtk3/extension.vala    |   5 +-
 ui/gtk3/panelbinding.vala |  65 ++----
 11 files changed, 863 insertions(+), 65 deletions(-)
 create mode 100644 src/ibusaccelgroup.c
 create mode 100644 src/ibusaccelgroup.h

diff --git a/bus/engineproxy.c b/bus/engineproxy.c
index cd4d54fd..175aec56 100644
--- a/bus/engineproxy.c
+++ b/bus/engineproxy.c
@@ -2,7 +2,7 @@
 /* vim:set et sts=4: */
 /* ibus - The Input Bus
  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2015-2016 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2008-2016 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -90,6 +90,7 @@ enum {
     CURSOR_DOWN_LOOKUP_TABLE,
     REGISTER_PROPERTIES,
     UPDATE_PROPERTY,
+    PANEL_EXTENSION,
     LAST_SIGNAL,
 };
 
@@ -370,6 +371,17 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class)
             1,
             IBUS_TYPE_PROPERTY);
 
+    engine_signals[PANEL_EXTENSION] =
+        g_signal_new (I_("panel-extension"),
+            G_TYPE_FROM_CLASS (class),
+            G_SIGNAL_RUN_LAST,
+            0,
+            NULL, NULL,
+            bus_marshal_VOID__VARIANT,
+            G_TYPE_NONE,
+            1,
+            G_TYPE_VARIANT);
+
     text_empty = ibus_text_new_from_static_string ("");
     g_object_ref_sink (text_empty);
 
@@ -631,6 +643,11 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
         return;
     }
 
+    if (g_strcmp0 (signal_name, "PanelExtension") == 0) {
+        g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, parameters);
+        return;
+    }
+
     g_return_if_reached ();
 }
 
diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
index 58d205cf..a4ce3d9d 100644
--- a/bus/ibusimpl.c
+++ b/bus/ibusimpl.c
@@ -302,9 +302,8 @@ _panel_destroy_cb (BusPanelProxy *panel,
 }
 
 static void
-_panel_panel_extension_cb (BusPanelProxy *panel,
-                           GVariant      *parameters,
-                           BusIBusImpl  *ibus)
+bus_ibus_impl_panel_extension_received (BusIBusImpl *ibus,
+                                        GVariant    *parameters)
 {
     if (!ibus->extension) {
         g_warning ("Panel extension is not running.");
@@ -323,6 +322,14 @@ _panel_panel_extension_cb (BusPanelProxy *panel,
                        -1, NULL, NULL, NULL);
 }
 
+static void
+_panel_panel_extension_cb (BusPanelProxy *panel,
+                           GVariant      *parameters,
+                           BusIBusImpl  *ibus)
+{
+    bus_ibus_impl_panel_extension_received (ibus, parameters);
+}
+
 static void
 _registry_changed_cb (IBusRegistry *registry,
                       BusIBusImpl  *ibus)
@@ -642,6 +649,21 @@ bus_ibus_impl_set_context_engine_from_desc (BusIBusImpl     *ibus,
                                           NULL);
 }
 
+static void
+_context_panel_extension_cb (BusInputContext *context,
+                             GVariant        *parameters,
+                             BusIBusImpl     *ibus)
+{
+    bus_ibus_impl_panel_extension_received (ibus, parameters);
+}
+
+const static struct {
+    const gchar *name;
+    GCallback    callback;
+} context_signals [] = {
+    { "panel-extension",             G_CALLBACK (_context_panel_extension_cb) }
+};
+
 /**
  * bus_ibus_impl_set_focused_context:
  *
@@ -651,6 +673,11 @@ static void
 bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
                                    BusInputContext *context)
 {
+    gint i;
+    BusEngineProxy *engine = NULL;
+    guint purpose = 0;
+    guint hints = 0;
+
     g_assert (BUS_IS_IBUS_IMPL (ibus));
     g_assert (context == NULL || BUS_IS_INPUT_CONTEXT (context));
     g_assert (context == NULL || bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS);
@@ -660,10 +687,6 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
         return;
     }
 
-    BusEngineProxy *engine = NULL;
-    guint purpose = 0;
-    guint hints = 0;
-
     if (ibus->focused_context) {
         if (ibus->use_global_engine) {
             /* dettach engine from the focused context */
@@ -681,6 +704,10 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
 
         bus_input_context_get_content_type (ibus->focused_context,
                                             &purpose, &hints);
+        for (i = 0; i < G_N_ELEMENTS(context_signals); i++) {
+            g_signal_handlers_disconnect_by_func (ibus->focused_context,
+                    context_signals[i].callback, ibus);
+        }
         g_object_unref (ibus->focused_context);
         ibus->focused_context = NULL;
     }
@@ -698,6 +725,12 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
             bus_input_context_set_engine (context, engine);
             bus_input_context_enable (context);
         }
+        for (i = 0; i < G_N_ELEMENTS(context_signals); i++) {
+            g_signal_connect (ibus->focused_context,
+                              context_signals[i].name,
+                              context_signals[i].callback,
+                              ibus);
+        }
 
         if (ibus->panel != NULL)
             bus_panel_proxy_focus_in (ibus->panel, context);
diff --git a/bus/inputcontext.c b/bus/inputcontext.c
index 4f2ecafc..a957d107 100644
--- a/bus/inputcontext.c
+++ b/bus/inputcontext.c
@@ -127,6 +127,7 @@ enum {
     ENGINE_CHANGED,
     REQUEST_ENGINE,
     SET_CONTENT_TYPE,
+    PANEL_EXTENSION,
     LAST_SIGNAL,
 };
 
@@ -598,6 +599,17 @@ bus_input_context_class_init (BusInputContextClass *class)
             G_TYPE_UINT,
             G_TYPE_UINT);
 
+    context_signals[PANEL_EXTENSION] =
+        g_signal_new (I_("panel-extension"),
+            G_TYPE_FROM_CLASS (class),
+            G_SIGNAL_RUN_LAST,
+            0,
+            NULL, NULL,
+            bus_marshal_VOID__VARIANT,
+            G_TYPE_NONE,
+            1,
+            G_TYPE_VARIANT);
+
     text_empty = ibus_text_new_from_string ("");
     g_object_ref_sink (text_empty);
     lookup_table_empty = ibus_lookup_table_new (9 /* page size */, 0, FALSE, FALSE);
@@ -2102,6 +2114,20 @@ _engine_update_property_cb (BusEngineProxy  *engine,
     bus_input_context_update_property (context, prop);
 }
 
+/**
+ * _engine_panel_extension_cb:
+ *
+ * A function to be called when "panel-extension" glib signal is sent
+ * from the engine object.
+ */
+static void
+_engine_panel_extension_cb (BusEngineProxy  *engine,
+                            GVariant        *parameters,
+                            BusInputContext *context)
+{
+    g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, parameters);
+}
+
 #define DEFINE_FUNCTION(name)                                   \
     static void                                                 \
     _engine_##name##_cb (BusEngineProxy   *engine,              \
@@ -2244,7 +2270,8 @@ const static struct {
     { "cursor-down-lookup-table", G_CALLBACK (_engine_cursor_down_lookup_table_cb) },
     { "register-properties",      G_CALLBACK (_engine_register_properties_cb) },
     { "update-property",          G_CALLBACK (_engine_update_property_cb) },
-    { "destroy",                  G_CALLBACK (_engine_destroy_cb) },
+    { "panel-extension",          G_CALLBACK (_engine_panel_extension_cb) },
+    { "destroy",                  G_CALLBACK (_engine_destroy_cb) }
 };
 
 static void
diff --git a/src/Makefile.am b/src/Makefile.am
index 72ec05ab..6a62e0f0 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -72,6 +72,7 @@ libibus_1_0_la_LDFLAGS =            \
     $(NULL)
 
 ibus_sources =              \
+    ibusaccelgroup.c        \
     ibusattribute.c         \
     ibusattrlist.c          \
     ibusbus.c               \
@@ -122,6 +123,7 @@ ibus_enumtypes_sources =    \
     $(NULL)
 ibus_headers =              \
     ibus.h                  \
+    ibusaccelgroup.h        \
     ibusattribute.h         \
     ibusattrlist.h          \
     ibusbus.h               \
diff --git a/src/ibus.h b/src/ibus.h
index b15dded9..256d57ba 100644
--- a/src/ibus.h
+++ b/src/ibus.h
@@ -60,6 +60,7 @@
 #include <ibusemoji.h>
 #include <ibusunicode.h>
 #include <ibusxevent.h>
+#include <ibusaccelgroup.h>
 
 #ifndef IBUS_DISABLE_DEPRECATED
 #include <ibuskeysyms-compat.h>
diff --git a/src/ibusaccelgroup.c b/src/ibusaccelgroup.c
new file mode 100644
index 00000000..8a81597e
--- /dev/null
+++ b/src/ibusaccelgroup.c
@@ -0,0 +1,529 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 2001 Tim Janik
+ *
+ * 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; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "ibusaccelgroup.h"
+#include "ibuskeys.h"
+#include "ibuskeysyms.h"
+
+
+/* for _gtk_get_primary_accel_mod() */
+#define _IBUS_GET_PRIMARY_ACCEL_MOD IBUS_CONTROL_MASK
+
+/**
+ * SECTION: ibusaccelgroup
+ * @short_description: Groups of global keyboard accelerators for an
+ *     entire GtkWindow
+ * @title: Accelerator Groups
+ * @stability: Unstable
+ *
+ * Provides ibus_accelerator_parse()
+ */
+
+
+/**
+ * ibus_accelerator_valid:
+ * @keyval: a GDK keyval
+ * @modifiers: modifier mask
+ *
+ * Determines whether a given keyval and modifier mask constitute
+ * a valid keyboard accelerator. For example, the #IBUS_KEY_a keyval
+ * plus #IBUS_CONTROL_MASK is valid - this is a “Ctrl+a” accelerator.
+ * But, you can't, for instance, use the #IBUS_KEY_Control_L keyval
+ * as an accelerator.
+ *
+ * Returns: %TRUE if the accelerator is valid
+ */
+gboolean
+ibus_accelerator_valid (guint           keyval,
+                        IBusModifierType modifiers)
+{
+    static const guint invalid_accelerator_vals[] = {
+        IBUS_KEY_Shift_L, IBUS_KEY_Shift_R, IBUS_KEY_Shift_Lock,
+        IBUS_KEY_Caps_Lock, IBUS_KEY_ISO_Lock, IBUS_KEY_Control_L,
+        IBUS_KEY_Control_R, IBUS_KEY_Meta_L, IBUS_KEY_Meta_R,
+        IBUS_KEY_Alt_L, IBUS_KEY_Alt_R, IBUS_KEY_Super_L, IBUS_KEY_Super_R,
+        IBUS_KEY_Hyper_L, IBUS_KEY_Hyper_R, IBUS_KEY_ISO_Level3_Shift,
+        IBUS_KEY_ISO_Next_Group, IBUS_KEY_ISO_Prev_Group,
+        IBUS_KEY_ISO_First_Group, IBUS_KEY_ISO_Last_Group,
+        IBUS_KEY_Mode_switch, IBUS_KEY_Num_Lock, IBUS_KEY_Multi_key,
+        IBUS_KEY_Scroll_Lock, IBUS_KEY_Sys_Req,
+        IBUS_KEY_Tab, IBUS_KEY_ISO_Left_Tab, IBUS_KEY_KP_Tab,
+        IBUS_KEY_First_Virtual_Screen, IBUS_KEY_Prev_Virtual_Screen,
+        IBUS_KEY_Next_Virtual_Screen, IBUS_KEY_Last_Virtual_Screen,
+        IBUS_KEY_Terminate_Server, IBUS_KEY_AudibleBell_Enable,
+        0
+    };
+    static const guint invalid_unmodified_vals[] = {
+        IBUS_KEY_Up, IBUS_KEY_Down, IBUS_KEY_Left, IBUS_KEY_Right,
+        IBUS_KEY_KP_Up, IBUS_KEY_KP_Down, IBUS_KEY_KP_Left, IBUS_KEY_KP_Right,
+        0
+    };
+    const guint *ac_val;
+
+    modifiers &= IBUS_MODIFIER_MASK;
+
+    if (keyval <= 0xFF)
+        return keyval >= 0x20;
+
+    ac_val = invalid_accelerator_vals;
+    while (*ac_val) {
+        if (keyval == *ac_val++)
+            return FALSE;
+    }
+
+    if (!modifiers) {
+        ac_val = invalid_unmodified_vals;
+        while (*ac_val) {
+            if (keyval == *ac_val++)
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static inline gboolean
+is_alt (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'a' || string[1] == 'A') &&
+            (string[2] == 'l' || string[2] == 'L') &&
+            (string[3] == 't' || string[3] == 'T') &&
+            (string[4] == '>'));
+}
+
+static inline gboolean
+is_ctl (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'c' || string[1] == 'C') &&
+            (string[2] == 't' || string[2] == 'T') &&
+            (string[3] == 'l' || string[3] == 'L') &&
+            (string[4] == '>'));
+}
+
+static inline gboolean
+is_modx (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'm' || string[1] == 'M') &&
+            (string[2] == 'o' || string[2] == 'O') &&
+            (string[3] == 'd' || string[3] == 'D') &&
+            (string[4] >= '1' && string[4] <= '5') &&
+            (string[5] == '>'));
+}
+
+static inline gboolean
+is_ctrl (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'c' || string[1] == 'C') &&
+            (string[2] == 't' || string[2] == 'T') &&
+            (string[3] == 'r' || string[3] == 'R') &&
+            (string[4] == 'l' || string[4] == 'L') &&
+            (string[5] == '>'));
+}
+
+static inline gboolean
+is_shft (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 's' || string[1] == 'S') &&
+            (string[2] == 'h' || string[2] == 'H') &&
+            (string[3] == 'f' || string[3] == 'F') &&
+            (string[4] == 't' || string[4] == 'T') &&
+            (string[5] == '>'));
+}
+
+static inline gboolean
+is_shift (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 's' || string[1] == 'S') &&
+            (string[2] == 'h' || string[2] == 'H') &&
+            (string[3] == 'i' || string[3] == 'I') &&
+            (string[4] == 'f' || string[4] == 'F') &&
+            (string[5] == 't' || string[5] == 'T') &&
+            (string[6] == '>'));
+}
+
+static inline gboolean
+is_control (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'c' || string[1] == 'C') &&
+            (string[2] == 'o' || string[2] == 'O') &&
+            (string[3] == 'n' || string[3] == 'N') &&
+            (string[4] == 't' || string[4] == 'T') &&
+            (string[5] == 'r' || string[5] == 'R') &&
+            (string[6] == 'o' || string[6] == 'O') &&
+            (string[7] == 'l' || string[7] == 'L') &&
+            (string[8] == '>'));
+}
+
+static inline gboolean
+is_release (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'r' || string[1] == 'R') &&
+            (string[2] == 'e' || string[2] == 'E') &&
+            (string[3] == 'l' || string[3] == 'L') &&
+            (string[4] == 'e' || string[4] == 'E') &&
+            (string[5] == 'a' || string[5] == 'A') &&
+            (string[6] == 's' || string[6] == 'S') &&
+            (string[7] == 'e' || string[7] == 'E') &&
+            (string[8] == '>'));
+}
+
+static inline gboolean
+is_meta (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'm' || string[1] == 'M') &&
+            (string[2] == 'e' || string[2] == 'E') &&
+            (string[3] == 't' || string[3] == 'T') &&
+            (string[4] == 'a' || string[4] == 'A') &&
+            (string[5] == '>'));
+}
+
+static inline gboolean
+is_super (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 's' || string[1] == 'S') &&
+            (string[2] == 'u' || string[2] == 'U') &&
+            (string[3] == 'p' || string[3] == 'P') &&
+            (string[4] == 'e' || string[4] == 'E') &&
+            (string[5] == 'r' || string[5] == 'R') &&
+            (string[6] == '>'));
+}
+
+static inline gboolean
+is_hyper (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'h' || string[1] == 'H') &&
+            (string[2] == 'y' || string[2] == 'Y') &&
+            (string[3] == 'p' || string[3] == 'P') &&
+            (string[4] == 'e' || string[4] == 'E') &&
+            (string[5] == 'r' || string[5] == 'R') &&
+            (string[6] == '>'));
+}
+
+static inline gboolean
+is_primary (const gchar *string)
+{
+    return ((string[0] == '<') &&
+            (string[1] == 'p' || string[1] == 'P') &&
+            (string[2] == 'r' || string[2] == 'R') &&
+            (string[3] == 'i' || string[3] == 'I') &&
+            (string[4] == 'm' || string[4] == 'M') &&
+            (string[5] == 'a' || string[5] == 'A') &&
+            (string[6] == 'r' || string[6] == 'R') &&
+            (string[7] == 'y' || string[7] == 'Y') &&
+            (string[8] == '>'));
+}
+
+static inline gboolean
+is_keycode (const gchar *string)
+{
+    return (string[0] == '0' &&
+            string[1] == 'x' &&
+            g_ascii_isxdigit (string[2]) &&
+            g_ascii_isxdigit (string[3]));
+}
+
+/**
+ * ibus_accelerator_parse:
+ * @accelerator: string representing an accelerator
+ * @accelerator_key: (out) (allow-none): return location for accelerator
+ *     keyval, or %NULL
+ * @accelerator_mods: (out) (allow-none): return location for accelerator
+ *     modifier mask, %NULL
+ *
+ * Parses a string representing an accelerator. The format looks like
+ * “<Control>a” or “<Shift><Alt>F1” or “<Release>z” (the last one is
+ * for key release).
+ *
+ * The parser is fairly liberal and allows lower or upper case, and also
+ * abbreviations such as “<Ctl>” and “<Ctrl>”. Key names are parsed using
+ * gdk_keyval_from_name(). For character keys the name is not the symbol,
+ * but the lowercase name, e.g. one would use “<Ctrl>minus” instead of
+ * “<Ctrl>-”.
+ *
+ * If the parse fails, @accelerator_key and @accelerator_mods will
+ * be set to 0 (zero).
+ *
+ * Since: 1.5.18
+ */
+void
+ibus_accelerator_parse (const gchar      *accelerator,
+                        guint            *accelerator_key,
+                        IBusModifierType *accelerator_mods)
+{
+    guint keyval;
+    IBusModifierType mods;
+    gint len;
+    gboolean error;
+
+    if (accelerator_key)
+        *accelerator_key = 0;
+    if (accelerator_mods)
+        *accelerator_mods = 0;
+    g_return_if_fail (accelerator != NULL);
+
+    error = FALSE;
+    keyval = 0;
+    mods = 0;
+    len = strlen (accelerator);
+    while (len) {
+        if (*accelerator == '<') {
+            if (len >= 9 && is_release (accelerator)) {
+                accelerator += 9;
+                len -= 9;
+                mods |= IBUS_RELEASE_MASK;
+            } else if (len >= 9 && is_primary (accelerator)) {
+                accelerator += 9;
+                len -= 9;
+                mods |= _IBUS_GET_PRIMARY_ACCEL_MOD;
+            } else if (len >= 9 && is_control (accelerator)) {
+                accelerator += 9;
+                len -= 9;
+                mods |= IBUS_CONTROL_MASK;
+            } else if (len >= 7 && is_shift (accelerator)) {
+                accelerator += 7;
+                len -= 7;
+                mods |= IBUS_SHIFT_MASK;
+            } else if (len >= 6 && is_shft (accelerator)) {
+                accelerator += 6;
+                len -= 6;
+                mods |= IBUS_SHIFT_MASK;
+            } else if (len >= 6 && is_ctrl (accelerator)) {
+                accelerator += 6;
+                len -= 6;
+                mods |= IBUS_CONTROL_MASK;
+            } else if (len >= 6 && is_modx (accelerator)) {
+                static const guint mod_vals[] = {
+                    IBUS_MOD1_MASK, IBUS_MOD2_MASK, IBUS_MOD3_MASK,
+                    IBUS_MOD4_MASK, IBUS_MOD5_MASK
+                };
+
+                len -= 6;
+                accelerator += 4;
+                mods |= mod_vals[*accelerator - '1'];
+                accelerator += 2;
+            } else if (len >= 5 && is_ctl (accelerator)) {
+                accelerator += 5;
+                len -= 5;
+                mods |= IBUS_CONTROL_MASK;
+            } else if (len >= 5 && is_alt (accelerator)) {
+                accelerator += 5;
+                len -= 5;
+                mods |= IBUS_MOD1_MASK;
+            } else if (len >= 6 && is_meta (accelerator)) {
+                accelerator += 6;
+                len -= 6;
+                mods |= IBUS_META_MASK;
+            } else if (len >= 7 && is_hyper (accelerator)) {
+                accelerator += 7;
+                len -= 7;
+                mods |= IBUS_HYPER_MASK;
+            } else if (len >= 7 && is_super (accelerator)) {
+                accelerator += 7;
+                len -= 7;
+                mods |= IBUS_SUPER_MASK;
+            } else {
+                gchar last_ch;
+
+                last_ch = *accelerator;
+                while (last_ch && last_ch != '>') {
+                    last_ch = *accelerator;
+                    accelerator += 1;
+                    len -= 1;
+                }
+            }
+        } else {
+            if (len >= 4 && is_keycode (accelerator)) {
+                /* There was a keycode in the string, but
+                 * we cannot store it, so we have an error */
+                error = TRUE;
+                goto out;
+            } else {
+	        keyval = ibus_keyval_from_name (accelerator);
+	        if (keyval == IBUS_KEY_VoidSymbol) {
+	            error = TRUE;
+	            goto out;
+		}
+	    }
+
+            accelerator += len;
+            len -= len;
+        }
+    }
+
+out:
+    if (error)
+        keyval = mods = 0;
+
+    if (accelerator_key)
+        *accelerator_key = ibus_keyval_to_lower (keyval);
+    if (accelerator_mods)
+        *accelerator_mods = mods;
+}
+
+/**
+ * ibus_accelerator_name:
+ * @accelerator_key: accelerator keyval
+ * @accelerator_mods: accelerator modifier mask
+ *
+ * Converts an accelerator keyval and modifier mask into a string
+ * parseable by gtk_accelerator_parse(). For example, if you pass in
+ * #IBUS_KEY_q and #IBUS_CONTROL_MASK, this function returns “<Control>q”.
+ *
+ * If you need to display accelerators in the user interface,
+ * see gtk_accelerator_get_label().
+ *
+ * Returns: a newly-allocated accelerator name
+ */
+gchar*
+ibus_accelerator_name (guint            accelerator_key,
+                       IBusModifierType accelerator_mods)
+{
+   static const gchar text_release[] = "<Release>";
+   static const gchar text_primary[] = "<Primary>";
+   static const gchar text_shift[] = "<Shift>";
+   static const gchar text_control[] = "<Control>";
+   static const gchar text_mod1[] = "<Alt>";
+   static const gchar text_mod2[] = "<Mod2>";
+   static const gchar text_mod3[] = "<Mod3>";
+   static const gchar text_mod4[] = "<Mod4>";
+   static const gchar text_mod5[] = "<Mod5>";
+   static const gchar text_meta[] = "<Meta>";
+   static const gchar text_super[] = "<Super>";
+   static const gchar text_hyper[] = "<Hyper>";
+   IBusModifierType saved_mods;
+   guint l;
+   const gchar *keyval_name;
+   gchar *accelerator;
+
+    accelerator_mods &= IBUS_MODIFIER_MASK;
+
+    keyval_name = ibus_keyval_name (ibus_keyval_to_lower (accelerator_key));
+    if (!keyval_name)
+        keyval_name = "";
+
+    saved_mods = accelerator_mods;
+    l = 0;
+    if (accelerator_mods & IBUS_RELEASE_MASK)
+        l += sizeof (text_release) - 1;
+    if (accelerator_mods & _IBUS_GET_PRIMARY_ACCEL_MOD) {
+        l += sizeof (text_primary) - 1;
+        /* consume the default accel */
+        accelerator_mods &= ~_IBUS_GET_PRIMARY_ACCEL_MOD;
+    }
+    if (accelerator_mods & IBUS_SHIFT_MASK)
+        l += sizeof (text_shift) - 1;
+    if (accelerator_mods & IBUS_CONTROL_MASK)
+        l += sizeof (text_control) - 1;
+    if (accelerator_mods & IBUS_MOD1_MASK)
+        l += sizeof (text_mod1) - 1;
+    if (accelerator_mods & IBUS_MOD2_MASK)
+        l += sizeof (text_mod2) - 1;
+    if (accelerator_mods & IBUS_MOD3_MASK)
+        l += sizeof (text_mod3) - 1;
+    if (accelerator_mods & IBUS_MOD4_MASK)
+        l += sizeof (text_mod4) - 1;
+    if (accelerator_mods & IBUS_MOD5_MASK)
+        l += sizeof (text_mod5) - 1;
+    l += strlen (keyval_name);
+    if (accelerator_mods & IBUS_META_MASK)
+        l += sizeof (text_meta) - 1;
+    if (accelerator_mods & IBUS_HYPER_MASK)
+        l += sizeof (text_hyper) - 1;
+    if (accelerator_mods & IBUS_SUPER_MASK)
+        l += sizeof (text_super) - 1;
+
+    accelerator = g_new (gchar, l + 1);
+
+    accelerator_mods = saved_mods;
+    l = 0;
+    accelerator[l] = 0;
+    if (accelerator_mods & IBUS_RELEASE_MASK) {
+        strcpy (accelerator + l, text_release);
+        l += sizeof (text_release) - 1;
+    }
+    if (accelerator_mods & _IBUS_GET_PRIMARY_ACCEL_MOD) {
+        strcpy (accelerator + l, text_primary);
+        l += sizeof (text_primary) - 1;
+        /* consume the default accel */
+        accelerator_mods &= ~_IBUS_GET_PRIMARY_ACCEL_MOD;
+    }
+    if (accelerator_mods & IBUS_SHIFT_MASK) {
+        strcpy (accelerator + l, text_shift);
+        l += sizeof (text_shift) - 1;
+    }
+    if (accelerator_mods & IBUS_CONTROL_MASK) {
+        strcpy (accelerator + l, text_control);
+        l += sizeof (text_control) - 1;
+    }
+    if (accelerator_mods & IBUS_MOD1_MASK) {
+        strcpy (accelerator + l, text_mod1);
+        l += sizeof (text_mod1) - 1;
+    }
+    if (accelerator_mods & IBUS_MOD2_MASK) {
+        strcpy (accelerator + l, text_mod2);
+        l += sizeof (text_mod2) - 1;
+    }
+    if (accelerator_mods & IBUS_MOD3_MASK) {
+        strcpy (accelerator + l, text_mod3);
+        l += sizeof (text_mod3) - 1;
+    }
+    if (accelerator_mods & IBUS_MOD4_MASK) {
+        strcpy (accelerator + l, text_mod4);
+        l += sizeof (text_mod4) - 1;
+    }
+    if (accelerator_mods & IBUS_MOD5_MASK) {
+        strcpy (accelerator + l, text_mod5);
+        l += sizeof (text_mod5) - 1;
+    }
+    if (accelerator_mods & IBUS_META_MASK) {
+        strcpy (accelerator + l, text_meta);
+        l += sizeof (text_meta) - 1;
+    }
+    if (accelerator_mods & IBUS_HYPER_MASK) {
+        strcpy (accelerator + l, text_hyper);
+        l += sizeof (text_hyper) - 1;
+    }
+    if (accelerator_mods & IBUS_SUPER_MASK) {
+        strcpy (accelerator + l, text_super);
+        l += sizeof (text_super) - 1;
+    }
+    strcpy (accelerator + l, keyval_name);
+
+    return accelerator;
+}
diff --git a/src/ibusaccelgroup.h b/src/ibusaccelgroup.h
new file mode 100644
index 00000000..cb38bee4
--- /dev/null
+++ b/src/ibusaccelgroup.h
@@ -0,0 +1,51 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 2001 Tim Janik
+ *
+ * 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; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __IBUS_ACCEL_GROUP_H_
+#define __IBUS_ACCEL_GROUP_H_
+
+
+#if !defined (__IBUS_H_INSIDE__) && !defined (IBUS_COMPILATION)
+#error "Only <ibus.h> can be included directly"
+#endif
+
+#include <glib.h>
+#include <ibustypes.h>
+
+G_BEGIN_DECLS
+
+
+/* --- Accelerators--- */
+gboolean ibus_accelerator_valid               (guint            keyval,
+                                               IBusModifierType modifiers)
+                                               G_GNUC_CONST;
+void     ibus_accelerator_parse               (const gchar      *accelerator,
+                                               guint            *accelerator_key,
+                                               IBusModifierType *accelerator_mods);
+gchar*   ibus_accelerator_name                (guint            accelerator_key,
+                                               IBusModifierType accelerator_mods);
+
+G_END_DECLS
+
+#endif /* __IBUS_ACCEL_GROUP_H_ */
diff --git a/src/ibusengine.c b/src/ibusengine.c
index da648d11..9a0b1a8a 100644
--- a/src/ibusengine.c
+++ b/src/ibusengine.c
@@ -20,12 +20,16 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
  * USA
  */
-#include "ibusengine.h"
 #include <stdarg.h>
 #include <string.h>
+
+#include "ibusaccelgroup.h"
+#include "ibusengine.h"
+#include "ibuskeysyms.h"
 #include "ibusmarshalers.h"
 #include "ibusinternal.h"
 #include "ibusshare.h"
+#include "ibusxevent.h"
 
 #define IBUS_ENGINE_GET_PRIVATE(o)  \
    (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_ENGINE, IBusEnginePrivate))
@@ -60,6 +64,8 @@ enum {
 };
 
 
+typedef struct _IBusEngineKeybinding IBusEngineKeybinding;
+
 /* IBusEnginePriv */
 struct _IBusEnginePrivate {
     gchar *engine_name;
@@ -74,11 +80,19 @@ struct _IBusEnginePrivate {
     /* cached content-type */
     guint content_purpose;
     guint content_hints;
+
+    GSettings             *settings_emoji;
+    IBusEngineKeybinding **emoji_keybindings;
+};
+
+struct _IBusEngineKeybinding {
+    guint            keyval;
+    IBusModifierType modifiers;
 };
 
 static guint            engine_signals[LAST_SIGNAL] = { 0 };
 
-static IBusText *text_empty = NULL;
+static IBusText *text_empty;
 
 /* functions prototype */
 static void      ibus_engine_destroy         (IBusEngine         *engine);
@@ -176,6 +190,11 @@ static void      ibus_engine_dbus_property_changed
                                              (IBusEngine         *engine,
                                               const gchar        *property_name,
                                               GVariant           *value);
+static void      ibus_engine_keybinding_free (IBusEngine         *engine);
+static void      settings_emoji_hotkey_changed_cb 
+                                             (GSettings          *settings,
+                                              const gchar        *key,
+                                              gpointer            data);
 
 
 G_DEFINE_TYPE (IBusEngine, ibus_engine, IBUS_TYPE_SERVICE)
@@ -263,11 +282,27 @@ static const gchar introspection_xml[] =
     "      <arg type='u' name='keycode' />"
     "      <arg type='u' name='state' />"
     "    </signal>"
+    "    <signal name='PanelExtension'>"
+    "      <arg type='v' name='data' />"
+    "    </signal>"
     /* FIXME properties */
     "    <property name='ContentType' type='(uu)' access='write' />"
     "  </interface>"
     "</node>";
 
+static const guint IBUS_MODIFIER_FILTER =
+        IBUS_MODIFIER_MASK & ~(
+        IBUS_LOCK_MASK |  /* Caps Lock */
+        IBUS_MOD2_MASK |  /* Num Lock */
+        IBUS_BUTTON1_MASK |
+        IBUS_BUTTON2_MASK |
+        IBUS_BUTTON3_MASK |
+        IBUS_BUTTON4_MASK |
+        IBUS_BUTTON5_MASK |
+        IBUS_SUPER_MASK |
+        IBUS_HYPER_MASK |
+        IBUS_META_MASK);
+
 static void
 ibus_engine_class_init (IBusEngineClass *class)
 {
@@ -802,9 +837,15 @@ ibus_engine_class_init (IBusEngineClass *class)
 static void
 ibus_engine_init (IBusEngine *engine)
 {
-    engine->priv = IBUS_ENGINE_GET_PRIVATE (engine);
-
-    engine->priv->surrounding_text = g_object_ref_sink (text_empty);
+    IBusEnginePrivate *priv;
+    engine->priv = priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    priv->surrounding_text = g_object_ref_sink (text_empty);
+    priv->settings_emoji =
+            g_settings_new ("org.freedesktop.ibus.panel.emoji");
+    settings_emoji_hotkey_changed_cb (priv->settings_emoji, "hotkey", engine);
+    g_signal_connect (priv->settings_emoji, "changed::hotkey",
+                      G_CALLBACK (settings_emoji_hotkey_changed_cb), engine);
 }
 
 static void
@@ -817,6 +858,7 @@ ibus_engine_destroy (IBusEngine *engine)
         g_object_unref (engine->priv->surrounding_text);
         engine->priv->surrounding_text = NULL;
     }
+    ibus_engine_keybinding_free (engine);
 
     IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine));
 }
@@ -852,6 +894,53 @@ ibus_engine_get_property (IBusEngine *engine,
     }
 }
 
+static void
+ibus_engine_panel_extension (IBusEngine *engine)
+{
+    IBusXEvent *xevent = ibus_x_event_new (
+            "event-type", IBUS_X_EVENT_KEY_PRESS,
+            "purpose", "emoji",
+            NULL);
+    GVariant *data = ibus_serializable_serialize_object (
+            IBUS_SERIALIZABLE (xevent));
+
+    g_assert (data != NULL);
+    ibus_engine_emit_signal (engine,
+                             "PanelExtension",
+                             g_variant_new ("(v)", data));
+}
+
+static gboolean
+ibus_engine_filter_key_event (IBusEngine *engine,
+                              guint       keyval,
+                              guint       keycode,
+                              guint       state)
+{
+    IBusEnginePrivate *priv;
+    int i;
+    guint modifiers;
+
+    if ((state & IBUS_RELEASE_MASK) != 0)
+        return FALSE;
+    g_return_val_if_fail (IBUS_IS_ENGINE (engine), FALSE);
+
+    priv = engine->priv;
+    modifiers = state & IBUS_MODIFIER_FILTER;
+    if (keyval >= IBUS_KEY_A && keyval <= IBUS_KEY_Z &&
+        (modifiers & IBUS_SHIFT_MASK) != 0) {
+        keyval = keyval - IBUS_KEY_A + IBUS_KEY_a;
+    }
+    for (i = 0; priv->emoji_keybindings[i]; i++) {
+        IBusEngineKeybinding *binding = priv->emoji_keybindings[i];
+        if (binding->keyval == keyval &&
+            binding->modifiers == modifiers) {
+            ibus_engine_panel_extension (engine);
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
 static gboolean
 ibus_engine_service_authorized_method (IBusService     *service,
                                        GDBusConnection *connection)
@@ -892,6 +981,7 @@ ibus_engine_service_method_call (IBusService           *service,
     if (g_strcmp0 (method_name, "ProcessKeyEvent") == 0) {
         guint keyval, keycode, state;
         gboolean retval = FALSE;
+
         g_variant_get (parameters, "(uuu)", &keyval, &keycode, &state);
         g_signal_emit (engine,
                        engine_signals[PROCESS_KEY_EVENT],
@@ -900,6 +990,12 @@ ibus_engine_service_method_call (IBusService           *service,
                        keycode,
                        state,
                        &retval);
+        if (!retval) {
+            retval = ibus_engine_filter_key_event (engine,
+                                                   keyval,
+                                                   keycode,
+                                                   state);
+        }
         g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", retval));
         return;
     }
@@ -1338,6 +1434,79 @@ ibus_engine_dbus_property_changed (IBusEngine  *engine,
     g_object_unref (message);
 }
 
+static void
+ibus_engine_keybinding_free (IBusEngine *engine)
+{
+    IBusEnginePrivate *priv;
+    int i;
+
+    g_return_if_fail (IBUS_IS_ENGINE (engine));
+
+    priv = engine->priv;
+    if (priv->emoji_keybindings) {
+        for (i = 0; priv->emoji_keybindings[i]; i++)
+            g_slice_free (IBusEngineKeybinding, priv->emoji_keybindings[i]);
+        g_clear_pointer (&priv->emoji_keybindings, g_free);
+    }
+}
+
+static IBusEngineKeybinding *
+ibus_engine_keybinding_new (IBusEngine  *engine,
+                            const gchar *accelerator)
+{
+    guint keyval = 0U;
+    IBusModifierType modifiers = 0;
+    IBusEngineKeybinding *binding = NULL;
+
+    ibus_accelerator_parse (accelerator, &keyval, &modifiers);
+    if (keyval == 0U && modifiers == 0) {
+        g_warning ("Failed to parse shortcut key '%s'", accelerator);
+        return NULL;
+    }
+    if (modifiers & IBUS_SUPER_MASK) {
+        modifiers^=IBUS_SUPER_MASK;
+        modifiers|=IBUS_MOD4_MASK;
+    }
+
+    binding = g_slice_new0 (IBusEngineKeybinding);
+    binding->keyval = keyval;
+    binding->modifiers = modifiers;
+    return binding;
+}
+
+static void
+settings_emoji_hotkey_changed_cb (GSettings   *settings,
+                                  const gchar *key,
+                                  gpointer     data)
+{
+    IBusEngine *engine;
+    IBusEnginePrivate *priv;
+    gchar **accelerators;
+    int i, j, length;
+    g_return_if_fail (IBUS_IS_ENGINE (data));
+    engine = IBUS_ENGINE (data);
+    priv = engine->priv;
+
+    if (g_strcmp0 (key, "hotkey") != 0)
+        return;
+    accelerators = g_settings_get_strv (settings, key);
+    length = g_strv_length (accelerators);
+    ibus_engine_keybinding_free (engine);
+    if (length == 0) {
+        g_strfreev (accelerators);
+        return;
+    }
+    priv->emoji_keybindings = g_new0 (IBusEngineKeybinding*, length + 1);
+    for (i = 0, j = 0; i < length; i++) {
+        IBusEngineKeybinding *binding =
+                ibus_engine_keybinding_new (engine, accelerators[i]);
+        if (!binding)
+            continue;
+        priv->emoji_keybindings[j++] = binding;
+    }
+    g_strfreev (accelerators);
+}
+
 IBusEngine *
 ibus_engine_new (const gchar     *engine_name,
                  const gchar     *object_path,
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index c85dfa86..8217a000 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -2062,6 +2062,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
         // Do not hide a bottom panel in XFCE4
         if (work_area.y < y)
             y = work_area.y;
+        // FIXME: move() does not work in Wayland
         move(x, y);
 
         uint32 timestamp = event.get_time();
diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala
index a170280b..03026d00 100644
--- a/ui/gtk3/extension.vala
+++ b/ui/gtk3/extension.vala
@@ -115,8 +115,9 @@ class ExtensionGtk {
         // and Ctrl-Shift-e when ibus-ui-gtk3 runs after the
         // desktop is launched.
         GLib.Environment.unset_variable("GDK_CORE_DEVICE_EVENTS");
-        // for Gdk.X11.get_default_xdisplay()
-        Gdk.set_allowed_backends("x11");
+        // Gdk.set_allowed_backends("x11") let present_with_time() failed on
+        // launching the dialog secondly in Wayland.
+        //Gdk.set_allowed_backends("x11");
 
         ExtensionGtk extension = new ExtensionGtk(argv);
         extension.run();
diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
index 1fbf6cfc..e3f39e12 100644
--- a/ui/gtk3/panelbinding.vala
+++ b/ui/gtk3/panelbinding.vala
@@ -33,8 +33,6 @@ class PanelBinding : IBus.PanelService {
     private string[] m_emojier_favorites = {};
     private Gtk.CssProvider m_css_provider;
     private const uint PRELOAD_ENGINES_DELAY_TIME = 30000;
-    private GLib.List<BindingCommon.Keybinding> m_keybindings =
-            new GLib.List<BindingCommon.Keybinding>();
     private bool m_load_emoji_at_startup;
     private bool m_loaded_emoji = false;
     private bool m_load_unicode_at_startup;
@@ -49,15 +47,6 @@ class PanelBinding : IBus.PanelService {
         m_bus = bus;
 
         init_settings();
-
-        bind_emoji_shortcut();
-    }
-
-
-    ~PanelBinding() {
-        BindingCommon.unbind_switch_shortcut(
-                BindingCommon.KeyEventFuncType.ANY,
-                m_keybindings);
     }
 
 
@@ -77,13 +66,6 @@ class PanelBinding : IBus.PanelService {
                                               ref m_css_provider);
         });
 
-        m_settings_emoji.changed["hotkey"].connect((key) => {
-                BindingCommon.unbind_switch_shortcut(
-                        BindingCommon.KeyEventFuncType.EMOJI_TYPING,
-                        m_keybindings);
-                bind_emoji_shortcut();
-        });
-
         m_settings_emoji.changed["font"].connect((key) => {
                 BindingCommon.set_custom_font(m_settings_panel,
                                               m_settings_emoji,
@@ -124,25 +106,6 @@ class PanelBinding : IBus.PanelService {
     }
 
 
-    private void bind_emoji_shortcut() {
-#if EMOJI_DICT
-        string[] accelerators = m_settings_emoji.get_strv("hotkey");
-
-        var keybinding_manager = KeybindingManager.get_instance();
-
-        foreach (var accelerator in accelerators) {
-            BindingCommon.keybinding_manager_bind(
-                    keybinding_manager,
-                    ref m_keybindings,
-                    accelerator,
-                    BindingCommon.KeyEventFuncType.EMOJI_TYPING,
-                    handle_emoji_typing,
-                    null);
-        }
-#endif
-    }
-
-
     private void set_emoji_favorites() {
         m_emojier_favorites = m_settings_emoji.get_strv("favorites");
         IBusEmojier.set_favorites(
@@ -195,9 +158,6 @@ class PanelBinding : IBus.PanelService {
 
         set_load_emoji_at_startup();
         set_load_unicode_at_startup();
-        BindingCommon.unbind_switch_shortcut(BindingCommon.KeyEventFuncType.ANY,
-                                             m_keybindings);
-        bind_emoji_shortcut();
         BindingCommon.set_custom_font(m_settings_panel,
                                       m_settings_emoji,
                                       ref m_css_provider);
@@ -354,19 +314,26 @@ class PanelBinding : IBus.PanelService {
             return;
         }
         Gdk.Event event = new Gdk.Event(event_type);
-        event.key.time = xevent.get_time();
-        Gdk.Display? display = Gdk.Display.get_default();
+        uint32 time = xevent.get_time();
+        if (time == 0)
+            time = Gtk.get_current_event_time();
+        event.key.time = time;
         X.Window xid = xevent.get_window();
-        Gdk.X11.Window window;
-        window = Gdk.X11.Window.lookup_for_display(
-                display as Gdk.X11.Display, xid);
-        if (window != null) {
-            event.key.window = window;
-        } else {
+        Gdk.Display? display = Gdk.Display.get_default();
+        Gdk.Window? window = null;
+        if (window == null && xid != 0) {
+            window = Gdk.X11.Window.lookup_for_display(
+                    display as Gdk.X11.Display, xid);
+        }
+        if (window == null && xid != 0) {
             window = new Gdk.X11.Window.foreign_for_display(
                     display as Gdk.X11.Display, xid);
-            event.key.window = window;
         }
+        if (window == null) {
+            window = Gdk.get_default_root_window();
+            window.ref();
+        }
+        event.key.window = window;
         handle_emoji_typing(event);
     }
 }
-- 
2.14.3