public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ibus] autotool: Added panel extension for emoji keybinding not to depen on desktops
@ 2026-05-31  2:06 Takao Fujiwara
  0 siblings, 0 replies; only message in thread
From: Takao Fujiwara @ 2026-05-31  2:06 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/ibus
Branch : autotool
Commit : 98919f74e387656fe5f8a4ba5eb8d59e612f246b
Author : Takao Fujiwara <tfujiwar@redhat.com>
Date   : 2018-02-21T16:07:48+09:00
Stats  : +4242/-51 in 6 file(s)
URL    : https://src.fedoraproject.org/rpms/ibus/c/98919f74e387656fe5f8a4ba5eb8d59e612f246b?branch=autotool

Log:
Added panel extension for emoji keybinding not to depen on desktops

---
diff --git a/.gitignore b/.gitignore
index cb853da..627f1d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@
 /ibus-po-1.5.9-20141001.tar.gz
 /ibus-po-1.5.10-20150402.tar.gz
 /ibus-po-1.5.14-20160909.tar.gz
+/ibus-po-1.5.17-20180221.tar.gz
 ibus-1.3.6.tar.gz
 /ibus-1.3.7.tar.gz
 /ibus-1.3.8.tar.gz

diff --git a/ibus-1385349-segv-bus-proxy.patch b/ibus-1385349-segv-bus-proxy.patch
index a9c1a73..02912a5 100644
--- a/ibus-1385349-segv-bus-proxy.patch
+++ b/ibus-1385349-segv-bus-proxy.patch
@@ -1,6 +1,6 @@
-From 8ea0d3f25078c612b4b16c955c1c0c17e764d8c5 Mon Sep 17 00:00:00 2001
+From 4ad2f160e2af0b71148b3f7726e71f26a107ff1c Mon Sep 17 00:00:00 2001
 From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 27 Jul 2017 18:56:01 +0900
+Date: Wed, 21 Feb 2018 15:05:18 +0900
 Subject: [PATCH] bus: Fix SEGV in bus_panel_proxy_focus_in()
 
 BUG=rhbz#1349148
@@ -10,11 +10,11 @@ BUG=rhbz#1406699
 BUG=rhbz#1432252
 ---
  bus/dbusimpl.c | 38 ++++++++++++++++++++++++++++++++------
- bus/ibusimpl.c | 22 +++++++++++++++++++---
- 2 files changed, 51 insertions(+), 9 deletions(-)
+ bus/ibusimpl.c | 21 ++++++++++++++++++---
+ 2 files changed, 50 insertions(+), 9 deletions(-)
 
 diff --git a/bus/dbusimpl.c b/bus/dbusimpl.c
-index b54ef81..e4dd868 100644
+index b54ef817..e4dd8683 100644
 --- a/bus/dbusimpl.c
 +++ b/bus/dbusimpl.c
 @@ -2,7 +2,8 @@
@@ -124,41 +124,37 @@ index b54ef81..e4dd868 100644
      if (incoming) {
          /* is incoming message */
 diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
-index f99307a..2d43ff6 100644
+index 58d205cf..34f6c909 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) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2017 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
-@@ -323,11 +324,14 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
-     g_assert (new_name != NULL);
-     g_assert (BUS_IS_IBUS_IMPL (ibus));
+@@ -357,13 +357,16 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+     else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION))
+         panel_type = PANEL_TYPE_EXTENSION;
  
--    if (g_strcmp0 (name, IBUS_SERVICE_PANEL) == 0) {
+-    if (panel_type != PANEL_TYPE_NONE) {
 +    do {
-+        if (g_strcmp0 (name, IBUS_SERVICE_PANEL) != 0)
++        if (panel_type == PANEL_TYPE_NONE)
 +            break;
          if (g_strcmp0 (new_name, "") != 0) {
              /* a Panel process is started. */
              BusConnection *connection;
              BusInputContext *context = NULL;
+             BusPanelProxy   **panel = (panel_type == PANEL_TYPE_PANEL) ?
+                                        &ibus->panel : &ibus->extension;
 +            GDBusConnection *dbus_connection = NULL;
  
-             if (ibus->panel != NULL) {
-                 ibus_proxy_destroy ((IBusProxy *) ibus->panel);
-@@ -338,6 +342,18 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
-             connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
+             if (*panel != NULL) {
+                 ibus_proxy_destroy ((IBusProxy *)(*panel));
+@@ -372,9 +375,21 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+                 g_assert (*panel == NULL);
+             }
+ 
+-            connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
++            connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS,
++                                                               new_name);
              g_return_if_fail (connection != NULL);
  
 +            dbus_connection = bus_connection_get_dbus_connection (connection);
-+
 +            /* rhbz#1349148 rhbz#1385349
 +             * Avoid SEGV of BUS_IS_PANEL_PROXY (ibus->panel)
 +             * This function is called during destroying the connection
@@ -169,10 +165,10 @@ index f99307a..2d43ff6 100644
 +                break;
 +            }
 +
-             ibus->panel = bus_panel_proxy_new (connection);
+             *panel = bus_panel_proxy_new (connection, panel_type);
  
-             g_signal_connect (ibus->panel,
-@@ -366,7 +382,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+             g_signal_connect (*panel,
+@@ -406,7 +421,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
                  }
              }
          }
@@ -182,5 +178,5 @@ index f99307a..2d43ff6 100644
      bus_ibus_impl_component_name_owner_changed (ibus, name, old_name, new_name);
  }
 -- 
-2.9.3
+2.14.3
 

diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index 6b21ef5..8d1fa9f 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -4442,3 +4442,4184 @@ index 555ea68f..0bf34da8 100644
 -- 
 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
+

diff --git a/ibus-xx-emoji-harfbuzz.patch b/ibus-xx-emoji-harfbuzz.patch
index 89a37b1..e948a6f 100644
--- a/ibus-xx-emoji-harfbuzz.patch
+++ b/ibus-xx-emoji-harfbuzz.patch
@@ -1,6 +1,6 @@
-From c6c1e8ea01c8466dc97d7549e77538e2d7ec872a Mon Sep 17 00:00:00 2001
+From 158e06a10726a10393f1f6dd7237457b0b601f84 Mon Sep 17 00:00:00 2001
 From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Mon, 29 Jan 2018 18:27:09 +0900
+Date: Wed, 21 Feb 2018 15:39:49 +0900
 Subject: [PATCH] Integrate custom rendering to use HarfBuzz glyph info
 
 IBusFontSet offers FcFontSet, glyph info with HarfBuzz and rendering
@@ -20,11 +20,11 @@ Need configure --enable-harfbuzz-for-emoji option to enable this feature.
  bindings/vala/Makefile.am              |   83 +++
  bindings/vala/ibus-fontset-1.0.deps    |    1 +
  configure.ac                           |   29 +
- ui/gtk3/Makefile.am                    |   32 +
+ ui/gtk3/Makefile.am                    |   36 ++
  ui/gtk3/emojier.vala                   |  111 ++++
  ui/gtk3/ibusfontset.c                  | 1030 ++++++++++++++++++++++++++++++++
  ui/gtk3/ibusfontset.h                  |  302 ++++++++++
- 8 files changed, 1589 insertions(+)
+ 8 files changed, 1593 insertions(+)
  create mode 100644 bindings/vala/IBusFontSet-1.0.metadata
  create mode 100644 bindings/vala/ibus-fontset-1.0.deps
  create mode 100644 ui/gtk3/ibusfontset.c
@@ -202,19 +202,19 @@ index bd41069b..243396ff 100644
  ])
  
 diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
-index 786b80e6..cd1e9c2c 100644
+index 0a8f4200..8bb2345d 100644
 --- a/ui/gtk3/Makefile.am
 +++ b/ui/gtk3/Makefile.am
-@@ -156,6 +156,8 @@ EXTRA_DIST =                            \
-     $(man_seven_in_files)               \
-     emojierapp.vala                     \
+@@ -159,6 +159,8 @@ EXTRA_DIST =                            \
+     extension.vala                      \
+     gtkextension.xml.in                 \
      gtkpanel.xml.in                     \
 +    ibusfontset.c                       \
 +    ibusfontset.h                       \
      notification-item.xml               \
      notification-watcher.xml            \
-     $(NULL)
-@@ -198,6 +200,36 @@ emojierapp.o: $(srcdir)/emojierapp.c
+     panelbinding.vala                   \
+@@ -247,6 +249,40 @@ panelbinding.o: $(srcdir)/panelbinding.c
  	$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
  	$(NULL)
  
@@ -227,6 +227,10 @@ index 786b80e6..cd1e9c2c 100644
 +    ibusfontset.c                               \
 +    $(NULL)
 +
++ibus_extension_gtk3_SOURCES +=                  \
++    ibusfontset.c                               \
++    $(NULL)
++
 +AM_CFLAGS += \
 +    @CAIRO_CFLAGS@                              \
 +    @FONTCONFIG_CFLAGS@                         \
@@ -252,7 +256,7 @@ index 786b80e6..cd1e9c2c 100644
  man_seven_DATA =$(man_seven_files:.7=.7.gz)
  man_sevendir = $(mandir)/man7
 diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 555ea68f..0a703383 100644
+index c85dfa86..86482543 100644
 --- a/ui/gtk3/emojier.vala
 +++ b/ui/gtk3/emojier.vala
 @@ -99,16 +99,103 @@ public class IBusEmojier : Gtk.ApplicationWindow {
@@ -367,7 +371,7 @@ index 555ea68f..0a703383 100644
      }
      private class ESelectedLabel : EWhiteLabel {
          public ESelectedLabel(string text) {
-@@ -307,6 +395,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -313,6 +401,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
      private static bool m_show_unicode = false;
      private static LoadProgressObject m_unicode_progress_object;
      private static bool m_loaded_unicode = false;
@@ -377,7 +381,7 @@ index 555ea68f..0a703383 100644
  
      private ThemedRGBA m_rgba;
      private Gtk.Box m_vbox;
-@@ -2064,6 +2155,22 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -2070,6 +2161,22 @@ public class IBusEmojier : Gtk.ApplicationWindow {
      }
  
  
@@ -400,7 +404,7 @@ index 555ea68f..0a703383 100644
      public static bool has_loaded_emoji_dict() {
          if (m_emoji_to_data_dict == null)
              return false;
-@@ -2094,6 +2201,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -2100,6 +2207,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
          int font_size = font_desc.get_size() / Pango.SCALE;
          if (font_size != 0)
              m_emoji_font_size = font_size;

diff --git a/ibus.spec b/ibus.spec
index 0b17381..455a95d 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -30,7 +30,7 @@
 
 Name:           ibus
 Version:        1.5.17
-Release:        8%{?dist}
+Release:        9%{?dist}
 Summary:        Intelligent Input Bus for Linux OS
 License:        LGPLv2+
 Group:          System Environment/Libraries
@@ -38,16 +38,17 @@ URL:            https://github.com/ibus/%name/wiki
 Source0:        https://github.com/ibus/%name/releases/download/%{version}/%{name}-%{version}.tar.gz
 Source1:        %{name}-xinput
 Source2:        %{name}.conf.5
+Source3:        https://fujiwara.fedorapeople.org/ibus/po/%{name}-po-1.5.17-20180221.tar.gz
 # Will remove the annotation tarball once the rpm is available on Fedora
 # Upstreamed patches.
 # Patch0:         %%{name}-HEAD.patch
 Patch0:         %{name}-HEAD.patch
-# Under testing #1349148 #1385349 #1350291 #1406699 #1432252
-Patch1:         %{name}-1385349-segv-bus-proxy.patch
 %if %with_emoji_harfbuzz
 # Under testing self rendering until Pango, Fontconfig, Cairo are stable
-Patch2:         %{name}-xx-emoji-harfbuzz.patch
+Patch1:         %{name}-xx-emoji-harfbuzz.patch
 %endif
+# Under testing #1349148 #1385349 #1350291 #1406699 #1432252
+Patch2:         %{name}-1385349-segv-bus-proxy.patch
 
 BuildRequires:  gettext-devel
 BuildRequires:  libtool
@@ -243,10 +244,12 @@ The ibus-devel-docs package contains developer documentation for IBus
 # %%patch0 -p1
 %patch0 -p1
 # cp client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c ||
-%patch1 -p1 -z .segv
 %if %with_emoji_harfbuzz
-%patch2 -p1 -z .hb
+%patch1 -p1 -z .hb
 %endif
+%patch2 -p1 -z .segv
+
+zcat %SOURCE3 | tar xfvp -
 
 # prep test
 diff client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c
@@ -360,6 +363,7 @@ dconf update || :
 %{_libexecdir}/ibus-engine-simple
 %{_libexecdir}/ibus-dconf
 %{_libexecdir}/ibus-portal
+%{_libexecdir}/ibus-extension-gtk3
 %{_libexecdir}/ibus-ui-emojier
 %{_libexecdir}/ibus-ui-gtk3
 %{_libexecdir}/ibus-x11
@@ -420,6 +424,10 @@ dconf update || :
 %{_datadir}/gtk-doc/html/*
 
 %changelog
+* Wed Feb 21 2018 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.17-9
+- Added panel extension for emoji keybinding not to depen on desktops
+- Showed Unicode code points on Unicode name list
+
 * Tue Feb 13 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 1.5.17-8
 - Remove useless requires
 

diff --git a/sources b/sources
index b11174b..5f3638d 100644
--- a/sources
+++ b/sources
@@ -1 +1,2 @@
-SHA512 (ibus-1.5.17.tar.gz) = 8a7e4fabbcb2096e647b1fb7487c92882bd320a4d777f2765817378abec2e60cafd63364c881fefc2805ff2baa6b28b15ee0710587662a3e65eeb60ead19496c
+8bb26453d0d1fa58e56c22668aaa8786  ibus-1.5.17.tar.gz
+c485c179e612ffd07cf6a6c567a928f1  ibus-po-1.5.17-20180221.tar.gz

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-05-31  2:06 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-31  2:06 [rpms/ibus] autotool: Added panel extension for emoji keybinding not to depen on desktops Takao Fujiwara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox