public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Takao Fujiwara <tfujiwar@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/ibus] autotool: Moved input focus on Emojier to engines' preedit
Date: Sun, 31 May 2026 02:06:51 GMT	[thread overview]
Message-ID: <178019321180.1.9681944304768863712.rpms-ibus-6784864abcdb@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/ibus
            Branch : autotool
            Commit : 6784864abcdbe87432235b803ed5a7a4484b5a8e
            Author : Takao Fujiwara <tfujiwar@redhat.com>
            Date   : 2018-06-20T19:07:39+09:00
            Stats  : +7789/-34 in 3 file(s)
            URL    : https://src.fedoraproject.org/rpms/ibus/c/6784864abcdbe87432235b803ed5a7a4484b5a8e?branch=autotool

            Log:
            Moved input focus on Emojier to engines' preedit

- Removed ibus-xx-emoji-harfbuzz.patch not to change session emoji font

---
diff --git a/ibus-1385349-segv-bus-proxy.patch b/ibus-1385349-segv-bus-proxy.patch
index 02912a5..995acdd 100644
--- a/ibus-1385349-segv-bus-proxy.patch
+++ b/ibus-1385349-segv-bus-proxy.patch
@@ -1,6 +1,6 @@
-From 4ad2f160e2af0b71148b3f7726e71f26a107ff1c Mon Sep 17 00:00:00 2001
+From 8c5ccd2c990080e581f6cf5c71d8f5603a87bf15 Mon Sep 17 00:00:00 2001
 From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Wed, 21 Feb 2018 15:05:18 +0900
+Date: Wed, 20 Jun 2018 17:40:15 +0900
 Subject: [PATCH] bus: Fix SEGV in bus_panel_proxy_focus_in()
 
 BUG=rhbz#1349148
@@ -124,12 +124,12 @@ index b54ef817..e4dd8683 100644
      if (incoming) {
          /* is incoming message */
 diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
-index 58d205cf..34f6c909 100644
+index ec1caea8..9ae3751b 100644
 --- a/bus/ibusimpl.c
 +++ b/bus/ibusimpl.c
-@@ -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;
+@@ -484,13 +484,16 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+     else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION_EMOJI))
+         panel_type = PANEL_TYPE_EXTENSION_EMOJI;
  
 -    if (panel_type != PANEL_TYPE_NONE) {
 +    do {
@@ -140,12 +140,12 @@ index 58d205cf..34f6c909 100644
              BusConnection *connection;
              BusInputContext *context = NULL;
              BusPanelProxy   **panel = (panel_type == PANEL_TYPE_PANEL) ?
-                                        &ibus->panel : &ibus->extension;
+                                       &ibus->panel : &ibus->emoji_extension;
 +            GDBusConnection *dbus_connection = NULL;
  
              if (*panel != NULL) {
                  ibus_proxy_destroy ((IBusProxy *)(*panel));
-@@ -372,9 +375,21 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+@@ -499,9 +502,21 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
                  g_assert (*panel == NULL);
              }
  
@@ -166,9 +166,9 @@ index 58d205cf..34f6c909 100644
 +            }
 +
              *panel = bus_panel_proxy_new (connection, panel_type);
- 
-             g_signal_connect (*panel,
-@@ -406,7 +421,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+             if (panel_type == PANEL_TYPE_EXTENSION_EMOJI)
+                 ibus->enable_emoji_extension = FALSE;
+@@ -555,7 +570,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
                  }
              }
          }

diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index 9939fd6..e95c344 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -1206,3 +1206,7773 @@ index 9a0b1a8a..fd61102a 100644
 -- 
 2.14.3
 
+From 196216a89a9167425dd9b41f4f1d8a494d370249 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 11 May 2018 19:13:03 +0900
+Subject: [PATCH] src: Add ibus-keypress
+
+---
+ configure.ac              |   8 ++
+ src/tests/Makefile.am     |   7 ++
+ src/tests/ibus-keypress.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++
+ src/tests/runtest         |   1 +
+ 4 files changed, 314 insertions(+)
+ create mode 100644 src/tests/ibus-keypress.c
+
+diff --git a/configure.ac b/configure.ac
+index 085cecb8..f332a775 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -621,6 +621,14 @@ if test x"$enable_libnotify" = x"yes"; then
+     enable_libnotify="yes (enabled, use --disable-libnotify to disable)"
+ fi
+ 
++PKG_CHECK_MODULES(XTEST,
++    [x11 xtst],
++    [enable_xtest=yes],
++    [enable_xtest=no]
++)
++AM_CONDITIONAL([ENABLE_XTEST], [test x"$enable_xtest" = x"yes"])
++
++
+ # --disable-emoji-dict option.
+ AC_ARG_ENABLE(emoji-dict,
+     AS_HELP_STRING([--disable-emoji-dict],
+diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
+index 11ebb531..5f21ebcd 100644
+--- a/src/tests/Makefile.am
++++ b/src/tests/Makefile.am
+@@ -61,6 +61,9 @@ endif
+ 
+ if ENABLE_GTK3
+ TESTS += ibus-compose
++if ENABLE_XTEST
++TESTS += ibus-keypress
++endif
+ endif
+ 
+ TESTS_ENVIRONMENT = \
+@@ -103,6 +106,10 @@ ibus_inputcontext_create_LDADD = $(prog_ldadd)
+ ibus_keynames_SOURCES = ibus-keynames.c
+ ibus_keynames_LDADD = $(prog_ldadd)
+ 
++ibus_keypress_SOURCES = ibus-keypress.c
++ibus_keypress_CFLAGS = @GTK3_CFLAGS@ @XTEST_CFLAGS@
++ibus_keypress_LDADD = $(prog_ldadd) @GTK3_LIBS@ @XTEST_LIBS@
++
+ ibus_registry_SOURCES = ibus-registry.c
+ ibus_registry_LDADD = $(prog_ldadd)
+ 
+diff --git a/src/tests/ibus-keypress.c b/src/tests/ibus-keypress.c
+new file mode 100644
+index 00000000..3486523b
+--- /dev/null
++++ b/src/tests/ibus-keypress.c
+@@ -0,0 +1,298 @@
++#include <gtk/gtk.h>
++#include <gdk/gdkx.h>
++#include "ibus.h"
++#include <stdlib.h>
++#include <X11/Xlib.h>
++#include <X11/extensions/XTest.h>
++
++#define GREEN "\033[0;32m"
++#define RED   "\033[0;31m"
++#define NC    "\033[0m"
++
++typedef struct _KeyData {
++    guint keyval;
++    guint modifiers;
++} KeyData;
++
++static const KeyData test_cases[][30] = {
++   { { IBUS_KEY_a, 0 }, { IBUS_KEY_comma, IBUS_SHIFT_MASK },
++     { IBUS_KEY_b, 0 }, { IBUS_KEY_period, IBUS_SHIFT_MASK },
++     { IBUS_KEY_c, 0 }, { IBUS_KEY_slash, IBUS_SHIFT_MASK },
++     { IBUS_KEY_d, 0 }, { IBUS_KEY_semicolon, IBUS_SHIFT_MASK },
++     { IBUS_KEY_e, 0 }, { IBUS_KEY_apostrophe, IBUS_SHIFT_MASK },
++     { IBUS_KEY_f, 0 }, { IBUS_KEY_bracketleft, IBUS_SHIFT_MASK },
++     { IBUS_KEY_g, 0 }, { IBUS_KEY_backslash, IBUS_SHIFT_MASK },
++     { 0, 0 } },
++   { { IBUS_KEY_grave, IBUS_SHIFT_MASK }, { IBUS_KEY_a, IBUS_SHIFT_MASK },
++     { IBUS_KEY_1, IBUS_SHIFT_MASK }, { IBUS_KEY_b, IBUS_SHIFT_MASK  },
++     { IBUS_KEY_2, IBUS_SHIFT_MASK }, { IBUS_KEY_c, IBUS_SHIFT_MASK  },
++     { IBUS_KEY_3, IBUS_SHIFT_MASK }, { IBUS_KEY_d, IBUS_SHIFT_MASK },
++     { IBUS_KEY_9, IBUS_SHIFT_MASK }, { IBUS_KEY_e, IBUS_SHIFT_MASK },
++     { IBUS_KEY_0, IBUS_SHIFT_MASK }, { IBUS_KEY_f, IBUS_SHIFT_MASK },
++     { IBUS_KEY_equal, IBUS_SHIFT_MASK }, { IBUS_KEY_g, IBUS_SHIFT_MASK },
++     { 0, 0 } },
++   { { 0, 0 } }
++};
++
++KeyData test_end_key = { IBUS_KEY_z, IBUS_SHIFT_MASK };
++
++static const gunichar test_results[][60] = {
++   { 'a', '<', 'b', '>', 'c', '?', 'd', ':', 'e', '"', 'f', '{', 'g', '|', 0 },
++   { '~', 'A', '!', 'B', '@', 'C', '#', 'D', '(', 'E', ')', 'F', '+', 'G', 0 },
++   { 0 }
++};
++
++
++IBusBus *m_bus;
++IBusEngine *m_engine;
++
++static gboolean window_focus_in_event_cb (GtkWidget     *entry,
++                                          GdkEventFocus *event,
++                                          gpointer       data);
++
++static IBusEngine *
++create_engine_cb (IBusFactory *factory, const gchar *name, gpointer data)
++{
++    static int i = 1;
++    gchar *engine_path =
++            g_strdup_printf ("/org/freedesktop/IBus/engine/simpletest/%d",
++                             i++);
++
++    m_engine = ibus_engine_new_with_type (IBUS_TYPE_ENGINE_SIMPLE,
++                                          name,
++                                          engine_path,
++                                          ibus_bus_get_connection (m_bus));
++    g_free (engine_path);
++    return m_engine;
++}
++
++static gboolean
++register_ibus_engine ()
++{
++    IBusFactory *factory;
++    IBusComponent *component;
++    IBusEngineDesc *desc;
++
++    m_bus = ibus_bus_new ();
++    if (!ibus_bus_is_connected (m_bus)) {
++        g_critical ("ibus-daemon is not running.");
++        return FALSE;
++    }
++    factory = ibus_factory_new (ibus_bus_get_connection (m_bus));
++    g_signal_connect (factory, "create-engine",
++                      G_CALLBACK (create_engine_cb), NULL);
++
++    component = ibus_component_new (
++            "org.freedesktop.IBus.SimpleTest",
++            "Simple Engine Test",
++            "0.0.1",
++            "GPL",
++            "Takao Fujiwara <takao.fujiwara1@gmail.com>",
++            "https://github.com/ibus/ibus/wiki",
++            "",
++            "ibus");
++    desc = ibus_engine_desc_new (
++            "xkbtest:us::eng",
++            "XKB Test",
++            "XKB Test",
++            "en",
++            "GPL",
++            "Takao Fujiwara <takao.fujiwara1@gmail.com>",
++            "ibus-engine",
++            "us");
++    ibus_component_add_engine (component, desc);
++    ibus_bus_register_component (m_bus, component);
++
++    return TRUE;
++}
++
++static gboolean
++finit (gpointer data)
++{
++    g_critical ("time out");
++    gtk_main_quit ();
++    return FALSE;
++}
++
++static void
++send_key_event (Display *xdisplay,
++                guint    keyval,
++                guint    modifiers)
++{
++    static struct {
++        guint   state;
++        KeySym  keysym;
++    } state2keysym[] = {
++        { IBUS_CONTROL_MASK, XK_Control_L } ,
++        { IBUS_MOD1_MASK,    XK_Alt_L },
++        { IBUS_MOD4_MASK,    XK_Super_L },
++        { IBUS_SHIFT_MASK,   XK_Shift_L },
++        { IBUS_LOCK_MASK,    XK_Caps_Lock },
++        { 0,           0L }
++    };
++    int i;
++    guint keycode;
++    guint state = modifiers;
++
++    while (state) {
++        for (i = 0; state2keysym[i].state; i++) {
++            if ((state2keysym[i].state & state) != 0) {
++                keycode = XKeysymToKeycode (xdisplay, state2keysym[i].keysym);
++                XTestFakeKeyEvent (xdisplay, keycode, True, CurrentTime);
++                XSync (xdisplay, False);
++                state ^= state2keysym[i].state;
++                break;
++            }
++        }
++    }
++    keycode = XKeysymToKeycode (xdisplay, keyval);
++    XTestFakeKeyEvent (xdisplay, keycode, True, CurrentTime);
++    XSync (xdisplay, False);
++    XTestFakeKeyEvent (xdisplay, keycode, False, CurrentTime);
++    XSync (xdisplay, False);
++
++    state = modifiers;
++    while (state) {
++        for (i = G_N_ELEMENTS (state2keysym) - 1; i >= 0; i--) {
++            if ((state2keysym[i].state & state) != 0) {
++                keycode = XKeysymToKeycode (xdisplay, state2keysym[i].keysym);
++                XTestFakeKeyEvent (xdisplay, keycode, False, CurrentTime);
++                XSync (xdisplay, False);
++                state ^= state2keysym[i].state;
++                break;
++            }
++        }
++    }
++}
++
++static void
++set_engine_cb (GObject      *object,
++               GAsyncResult *res,
++               gpointer      data)
++{
++    IBusBus *bus = IBUS_BUS (object);
++    GtkWidget *entry = GTK_WIDGET (data);
++    GdkDisplay *display;
++    Display *xdisplay;
++    GError *error = NULL;
++    int i, j;
++
++    g_assert (GTK_IS_ENTRY (entry));
++
++    if (!ibus_bus_set_global_engine_async_finish (bus, res, &error)) {
++        g_critical ("set engine failed: %s", error->message);
++        g_error_free (error);
++        return;
++    }
++
++    display = gtk_widget_get_display (entry);
++    if (GDK_IS_X11_DISPLAY (display)) {
++        xdisplay = gdk_x11_display_get_xdisplay (display);
++    } else {
++#if 0
++        xdisplay = XOpenDisplay (NULL);
++#else
++        g_critical ("No idea to simulate key events in Wayland\n");
++#endif
++    }
++    g_return_if_fail (xdisplay);
++
++    for (i = 0; test_cases[i][0].keyval; i++) {
++        for (j = 0; test_cases[i][j].keyval; j++) {
++            send_key_event (xdisplay,
++                            test_cases[i][j].keyval,
++                            test_cases[i][j].modifiers);
++        }
++        send_key_event (xdisplay, test_end_key.keyval, test_end_key.modifiers);
++    }
++
++    g_timeout_add_seconds (10, finit, NULL);
++}
++
++static gboolean
++window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data)
++{
++    g_assert (m_bus != NULL);
++    ibus_bus_set_global_engine_async (m_bus,
++                                      "xkbtest:us::eng",
++                                      -1,
++                                      NULL,
++                                      set_engine_cb,
++                                      entry);
++    return FALSE;
++}
++
++static void
++window_inserted_text_cb (GtkEntryBuffer *buffer,
++                         guint           position,
++                         const gchar    *chars,
++                         guint           nchars,
++                         gpointer        data)
++{
++    GtkWidget *entry = data;
++    static int i = 0;
++    static int j = 0;
++
++    if (g_utf8_get_char (chars) == 'Z') {
++        int k;
++        g_print ("\n" GREEN "PASS" NC ": ");
++        for (k = 0; k < j; k++)
++            g_print ("%lc(%X) ", test_results[i][k], test_results[i][k]);
++        g_print ("\n");
++        i++;
++        j = 0;
++        if (test_results[i][0] == 0)
++            gtk_main_quit ();
++        else
++            gtk_entry_set_text (GTK_ENTRY (entry), "");
++        return;
++    }
++    g_assert (g_utf8_get_char (chars) == test_results[i][j]);
++    j++;
++}
++
++static void
++create_window ()
++{
++    GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
++    GtkWidget *entry = gtk_entry_new ();
++    GtkEntryBuffer *buffer;
++
++    g_signal_connect (window, "destroy",
++                      G_CALLBACK (gtk_main_quit), NULL);
++    g_signal_connect (entry, "focus-in-event",
++                      G_CALLBACK (window_focus_in_event_cb), NULL);
++    buffer = gtk_entry_get_buffer (GTK_ENTRY (entry));
++    g_signal_connect (buffer, "inserted-text",
++                      G_CALLBACK (window_inserted_text_cb), entry);
++    gtk_container_add (GTK_CONTAINER (window), entry);
++    gtk_widget_show_all (window);
++}
++
++static void
++test_keypress (void)
++{
++    int status = 0;
++    GError *error = NULL;
++
++    g_spawn_command_line_sync ("setxkbmap -layout us",
++                               NULL, NULL,
++                               &status, &error);
++    g_assert (register_ibus_engine ());
++
++    create_window ();
++    gtk_main ();
++}
++
++int
++main (int argc, char *argv[])
++{
++    ibus_init ();
++    g_test_init (&argc, &argv, NULL);
++    gtk_init (&argc, &argv);
++
++    g_test_add_func ("/ibus/keyrepss", test_keypress);
++
++
++    return g_test_run ();
++}
+diff --git a/src/tests/runtest b/src/tests/runtest
+index 35825b1b..b6b845d6 100755
+--- a/src/tests/runtest
++++ b/src/tests/runtest
+@@ -32,6 +32,7 @@ ibus-inputcontext
+ ibus-inputcontext-create
+ ibus-engine-switch
+ ibus-compose
++ibus-keypress
+ test-stress
+ "
+ IBUS_SCHEMA_FILE='org.freedesktop.ibus.gschema.xml'
+-- 
+2.14.3
+
+From 8ab0b603ba1cd8701583aee46c712898d52005f1 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Wed, 23 May 2018 19:20:10 +0900
+Subject: [PATCH] bus: Fix a SEGV in bus_input_context_emit_signal
+
+IBus engines can call 'RequireSurroundingText' for a fake input context
+if there is no input focus.
+---
+ bus/inputcontext.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/bus/inputcontext.c b/bus/inputcontext.c
+index a957d107..dfb98c36 100644
+--- a/bus/inputcontext.c
++++ b/bus/inputcontext.c
+@@ -716,7 +716,9 @@ bus_input_context_emit_signal (BusInputContext *context,
+                                GError         **error)
+ {
+     if (context->connection == NULL) {
+-        g_variant_unref (parameters);
++        /* fake context has no connections. */
++        if (parameters)
++            g_variant_unref (parameters);
+         return TRUE;
+     }
+ 
+-- 
+2.14.3
+
+From a1f91b27145b046a112bb5eba2561880dae5d6a2 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 4 Jun 2018 17:44:17 +0900
+Subject: [PATCH] ui/gtk3: Get PangoAttrList of auxiliary text from
+ IBusText
+
+Since IBus auxiliary text would be one line, it's better to show the
+character attributes likes color, italic, bold, on the auxiliary text.
+
+Also deleted the cursor width from the X position of CandidatePanel
+because IBus preedit overrides the original cursor of the applications.
+---
+ ui/gtk3/candidatepanel.vala | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/ui/gtk3/candidatepanel.vala b/ui/gtk3/candidatepanel.vala
+index ec2d3db4..d404c659 100644
+--- a/ui/gtk3/candidatepanel.vala
++++ b/ui/gtk3/candidatepanel.vala
+@@ -3,7 +3,7 @@
+  * ibus - The Input Bus
+  *
+  * Copyright(c) 2011-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>
+  *
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Lesser General Public
+@@ -153,6 +153,8 @@ public class CandidatePanel : Gtk.Box{
+     public void set_auxiliary_text(IBus.Text? text) {
+         if (text != null) {
+             m_aux_label.set_text(text.get_text());
++            Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text);
++            m_aux_label.set_attributes(attrs);
+             m_aux_label.show();
+         } else {
+             m_aux_label.set_text("");
+@@ -314,7 +316,7 @@ public class CandidatePanel : Gtk.Box{
+ 
+     private void adjust_window_position_horizontal() {
+         Gdk.Point cursor_right_bottom = {
+-                m_cursor_location.x + m_cursor_location.width,
++                m_cursor_location.x,
+                 m_cursor_location.y + m_cursor_location.height
+         };
+ 
+-- 
+2.14.3
+
+From cf4e2f1d815b700b0470380e0ff428ff266cc18a Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Thu, 14 Jun 2018 17:29:06 +0900
+Subject: [PATCH] bus: Rename panel-extension to emoji-extension for CLI
+
+---
+ bus/main.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/bus/main.c b/bus/main.c
+index e1cc423b..2fb37b69 100644
+--- a/bus/main.c
++++ b/bus/main.c
+@@ -43,7 +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 *emoji_extension = "default";
+ static gchar *config = "default";
+ static gchar *desktop = "gnome";
+ 
+@@ -67,7 +67,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" },
++    { "emoji-extension", 'E', 0, G_OPTION_ARG_STRING, &emoji_extension, "specify the cmdline of emoji 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 },
+@@ -245,7 +245,7 @@ main (gint argc, gchar **argv)
+     bus_server_init ();
+     for (i = 0; i < G_N_ELEMENTS(panel_extension_disable_users); i++) {
+         if (!g_strcmp0 (username, panel_extension_disable_users[i]) != 0) {
+-            panel_extension = "disable";
++            emoji_extension = "disable";
+             break;
+         }
+     }
+@@ -286,7 +286,7 @@ main (gint argc, gchar **argv)
+     }
+ 
+ #ifdef EMOJI_DICT
+-    if (g_strcmp0 (panel_extension, "default") == 0) {
++    if (g_strcmp0 (emoji_extension, "default") == 0) {
+         BusComponent *component;
+         component = bus_ibus_impl_lookup_component_by_name (
+                 BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION);
+@@ -298,9 +298,9 @@ main (gint argc, gchar **argv)
+             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))
++    } else if (g_strcmp0 (emoji_extension, "disable") != 0 &&
++               g_strcmp0 (emoji_extension, "") != 0) {
++        if (!execute_cmdline (emoji_extension))
+             exit (-1);
+     }
+ #endif
+-- 
+2.14.3
+
+From ddc2284200971141947a37057356b4bbd84be7ce Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Thu, 14 Jun 2018 18:30:46 +0900
+Subject: [PATCH] tools: Add ibus read-config --engine-id option for engine
+ schemas
+
+Fixed ibus read-config and reset-config options and also added --engine-id
+sub option for engine schemas.
+E.g.
+% ibus read-config --engine-id anthy
+% ibus read-config --engine-id com.github.libpinyin.ibus-libpinyin.libpinyin
+---
+ tools/main.vala | 99 +++++++++++++++++++++++++++++++++++++++++++++++++--------
+ 1 file changed, 85 insertions(+), 14 deletions(-)
+
+diff --git a/tools/main.vala b/tools/main.vala
+index 8c0b64d3..6e201f30 100644
+--- a/tools/main.vala
++++ b/tools/main.vala
+@@ -3,7 +3,7 @@
+  * ibus - The Input Bus
+  *
+  * Copyright(c) 2013 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>
+  *
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Lesser General Public
+@@ -22,20 +22,17 @@
+  */
+ 
+ private const string IBUS_SCHEMAS_GENERAL = "org.freedesktop.ibus.general";
+-private const string IBUS_SCHEMAS_GENERAL_PANEL =
+-        "org.freedesktop.ibus.general.panel";
++private const string IBUS_SCHEMAS_GENERAL_HOTKEY =
++        "org.freedesktop.ibus.general.hotkey";
+ private const string IBUS_SCHEMAS_PANEL = "org.freedesktop.ibus.panel";
+-
+-private const string[] IBUS_SCHEMAS = {
+-    IBUS_SCHEMAS_GENERAL,
+-    IBUS_SCHEMAS_GENERAL_PANEL,
+-    IBUS_SCHEMAS_PANEL,
+-};
++private const string IBUS_SCHEMAS_PANEL_EMOJI =
++        "org.freedesktop.ibus.panel.emoji";
+ 
+ bool name_only = false;
+ /* system() exists as a public API. */
+ bool is_system = false;
+ string cache_file = null;
++string engine_id = null;
+ 
+ class EngineList {
+     public IBus.EngineDesc[] data = {};
+@@ -292,15 +289,78 @@ int print_address(string[] argv) {
+     return Posix.EXIT_SUCCESS;
+ }
+ 
++private int read_config_options(string[] argv) {
++    const OptionEntry[] options = {
++        { "engine-id", 0, 0, OptionArg.STRING, out engine_id,
++          N_("Use engine schema paths instead of ibus core, " +
++             "which can be comma-separated values."), "ENGINE_ID" },
++        { null }
++    };
++
++    var option = new OptionContext();
++    option.add_main_entries(options, Config.GETTEXT_PACKAGE);
++
++    try {
++        option.parse(ref argv);
++    } catch (OptionError e) {
++        stderr.printf("%s\n", e.message);
++        return Posix.EXIT_FAILURE;
++    }
++    return Posix.EXIT_SUCCESS;
++}
++
++private GLib.SList<string> get_ibus_schemas() {
++    string[] ids = {};
++    if (engine_id != null) {
++        ids = engine_id.split(",");
++    }
++    GLib.SList<string> ibus_schemas = new GLib.SList<string>();
++    GLib.SettingsSchemaSource schema_source =
++            GLib.SettingsSchemaSource.get_default();
++    string[] list_schemas = {};
++    schema_source.list_schemas(true, out list_schemas, null);
++    foreach (string schema in list_schemas) {
++        if (ids.length != 0) {
++            foreach (unowned string id in ids) {
++                if (id == schema ||
++                    schema.has_prefix("org.freedesktop.ibus.engine." + id)) {
++                    ibus_schemas.prepend(schema);
++                    break;
++                }
++            }
++        } else if (schema.has_prefix("org.freedesktop.ibus") &&
++            !schema.has_prefix("org.freedesktop.ibus.engine")) {
++            ibus_schemas.prepend(schema);
++        }
++    }
++    if (ibus_schemas.length() == 0) {
++        printerr("Not found schemas of \"org.freedesktop.ibus\"\n");
++        return ibus_schemas;
++    }
++    ibus_schemas.sort(GLib.strcmp);
++
++    return ibus_schemas;
++}
++
+ int read_config(string[] argv) {
+-    var output = new GLib.StringBuilder();
++    if (read_config_options(argv) == Posix.EXIT_FAILURE)
++        return Posix.EXIT_FAILURE;
++
++    GLib.SList<string> ibus_schemas = get_ibus_schemas();
++    if (ibus_schemas.length() == 0)
++        return Posix.EXIT_FAILURE;
+ 
+-    foreach (string schema in IBUS_SCHEMAS) {
++    GLib.SettingsSchemaSource schema_source =
++            GLib.SettingsSchemaSource.get_default();
++    var output = new GLib.StringBuilder();
++    foreach (string schema in ibus_schemas) {
++        GLib.SettingsSchema settings_schema = schema_source.lookup(schema,
++                                                                   false);
+         GLib.Settings settings = new GLib.Settings(schema);
+ 
+         output.append_printf("SCHEMA: %s\n", schema);
+ 
+-        foreach (string key in settings.list_keys()) {
++        foreach (string key in settings_schema.list_keys()) {
+             GLib.Variant variant = settings.get_value(key);
+             output.append_printf("  %s: %s\n", key, variant.print(true));
+         }
+@@ -311,14 +371,25 @@ int read_config(string[] argv) {
+ }
+ 
+ int reset_config(string[] argv) {
++    if (read_config_options(argv) == Posix.EXIT_FAILURE)
++        return Posix.EXIT_FAILURE;
++
++    GLib.SList<string> ibus_schemas = get_ibus_schemas();
++    if (ibus_schemas.length() == 0)
++        return Posix.EXIT_FAILURE;
++
+     print("%s\n", _("Resetting…"));
+ 
+-    foreach (string schema in IBUS_SCHEMAS) {
++    GLib.SettingsSchemaSource schema_source =
++            GLib.SettingsSchemaSource.get_default();
++    foreach (string schema in ibus_schemas) {
++        GLib.SettingsSchema settings_schema = schema_source.lookup(schema,
++                                                                   false);
+         GLib.Settings settings = new GLib.Settings(schema);
+ 
+         print("SCHEMA: %s\n", schema);
+ 
+-        foreach (string key in settings.list_keys()) {
++        foreach (string key in settings_schema.list_keys()) {
+             print("  %s\n", key);
+             settings.reset(key);
+         }
+-- 
+2.14.3
+
+From 37aa95f1adcdde82ef473936cadc0fa3fe8a4e44 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 15 Jun 2018 19:23:27 +0900
+Subject: [PATCH] setup: Replace GtkTable /w GtkGrid
+
+---
+ setup/setup.ui | 113 +++++++++++++++++++++------------------------------------
+ 1 file changed, 41 insertions(+), 72 deletions(-)
+
+diff --git a/setup/setup.ui b/setup/setup.ui
+index 322f5146..e64b1046 100644
+--- a/setup/setup.ui
++++ b/setup/setup.ui
+@@ -99,11 +99,9 @@
+                     <property name="label_xalign">0</property>
+                     <property name="shadow_type">none</property>
+                     <child>
+-                      <object class="GtkTable" id="table1">
++                      <object class="GtkGrid" id="table1">
+                         <property name="visible">True</property>
+                         <property name="can_focus">False</property>
+-                        <property name="n_rows">5</property>
+-                        <property name="n_columns">2</property>
+                         <property name="column_spacing">12</property>
+                         <property name="row_spacing">6</property>
+                         <property name="margin_top">6</property>
+@@ -117,8 +115,8 @@
+                             <property name="label" translatable="yes">Next input method:</property>
+                           </object>
+                           <packing>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="left_attach">0</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -131,10 +129,8 @@
+                             <property name="label" translatable="yes">Previous input method:</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">1</property>
+-                            <property name="bottom_attach">2</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -143,6 +139,7 @@
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="spacing">6</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkEntry" id="entry_switch_engine">
+                                 <property name="visible">True</property>
+@@ -174,8 +171,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -184,6 +180,7 @@
+                             <property name="no_show_all">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="spacing">6</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkEntry" id="entry_prev_engine">
+   >                                     <property name="no_show_all">True</property>
+@@ -217,10 +214,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">1</property>
+-                            <property name="bottom_attach">2</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -232,10 +226,8 @@
+                             <property name="label" translatable="yes">Enable or disable:</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">2</property>
+-                            <property name="bottom_attach">3</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -246,10 +238,8 @@
+                             <property name="label" translatable="yes">Enable:</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">3</property>
+-                            <property name="bottom_attach">4</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -258,6 +248,7 @@
+                             <property name="no_show_all">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="spacing">6</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkEntry" id="entry_enable_unconditional">
+                                 <property name="visible">True</property>
+@@ -289,10 +280,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">3</property>
+-                            <property name="bottom_attach">4</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -303,10 +291,8 @@
+                             <property name="label" translatable="yes">Disable:</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">4</property>
+-                            <property name="bottom_attach">5</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -315,6 +301,7 @@
+                             <property name="no_show_all">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="spacing">6</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkEntry" id="entry_disable_unconditional">
+                                 <property name="visible">True</property>
+@@ -346,10 +333,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">4</property>
+-                            <property name="bottom_attach">5</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                       </object>
+@@ -376,11 +360,9 @@
+                     <property name="label_xalign">0</property>
+                     <property name="shadow_type">none</property>
+                     <child>
+-                      <object class="GtkTable" id="table2">
++                      <object class="GtkGrid" id="table2">
+                         <property name="visible">True</property>
+                         <property name="can_focus">False</property>
+-                        <property name="n_rows">7</property>
+-                        <property name="n_columns">2</property>
+                         <property name="column_spacing">12</property>
+                         <property name="row_spacing">6</property>
+                         <property name="margin_top">6</property>
+@@ -393,10 +375,11 @@
+                             <property name="halign">start</property>
+                             <property name="label" translatable="yes">Candidates orientation:</property>
+                             <property name="justify">right</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="left_attach">0</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -404,6 +387,7 @@
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="model">model_candidates_orientation</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkCellRendererText" id="renderer1"/>
+                               <attributes>
+@@ -413,8 +397,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -425,12 +408,11 @@
+                             <property name="halign">start</property>
+                             <property name="label" translatable="yes">Show property panel:</property>
+                             <property name="justify">right</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">1</property>
+-                            <property name="bottom_attach">2</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -440,12 +422,11 @@
+                             <property name="halign">start</property>
+                             <property name="label" translatable="yes">Language panel position:</property>
+                             <property name="justify">right</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">2</property>
+-                            <property name="bottom_attach">3</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -453,6 +434,7 @@
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="model">model_panel_show_mode</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkCellRendererText" id="renderer2"/>
+                               <attributes>
+@@ -462,10 +444,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">1</property>
+-                            <property name="bottom_attach">2</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -473,6 +452,7 @@
+                             <property name="can_focus">False</property>
+                             <property name="no_show_all">True</property>
+                             <property name="model">model_panel_position</property>
++                            <property name="hexpand">True</property>
+                             <child>
+                               <object class="GtkCellRendererText" id="renderer3"/>
+                               <attributes>
+@@ -482,10 +462,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">2</property>
+-                            <property name="bottom_attach">3</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -499,13 +476,12 @@
+                             <property name="use_action_appearance">False</property>
+                             <property name="halign">start</property>
+                             <property name="draw_indicator">True</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
+-                            <property name="right_attach">2</property>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">3</property>
+-                            <property name="bottom_attach">4</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="width">2</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -519,13 +495,12 @@
+                             <property name="use_action_appearance">False</property>
+                             <property name="halign">start</property>
+                             <property name="draw_indicator">True</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
+-                            <property name="right_attach">2</property>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">4</property>
+-                            <property name="bottom_attach">5</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="width">2</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -539,13 +514,12 @@
+                             <property name="use_action_appearance">False</property>
+                             <property name="halign">start</property>
+                             <property name="draw_indicator">True</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
+-                            <property name="right_attach">2</property>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">5</property>
+-                            <property name="bottom_attach">6</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="width">2</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -559,12 +533,11 @@
+                             <property name="use_underline">True</property>
+                             <property name="halign">start</property>
+                             <property name="draw_indicator">True</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
++                            <property name="left_attach">0</property>
+                             <property name="top_attach">6</property>
+-                            <property name="bottom_attach">7</property>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -573,13 +546,11 @@
+                             <property name="can_focus">True</property>
+                             <property name="receives_default">True</property>
+                             <property name="use_action_appearance">False</property>
++                            <property name="hexpand">True</property>
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+                             <property name="top_attach">6</property>
+-                            <property name="bottom_attach">7</property>
+-                            <property name="y_options">GTK_FILL</property>
+                           </packing>
+                         </child>
+                       </object>
+@@ -888,11 +859,9 @@
+                     <property name="label_xalign">0</property>
+                     <property name="shadow_type">none</property>
+                     <child>
+-                      <object class="GtkTable" id="table_emoji1">
++                      <object class="GtkGrid" id="table_emoji1">
+                         <property name="visible">True</property>
+                         <property name="can_focus">False</property>
+-                        <property name="n_rows">5</property>
+-                        <property name="n_columns">2</property>
+                         <property name="column_spacing">12</property>
+                         <property name="row_spacing">6</property>
+                         <property name="margin_top">6</property>
+@@ -906,8 +875,8 @@
+                             <property name="label" translatable="yes">Emoji choice:</property>
+                           </object>
+                           <packing>
+-                            <property name="x_options">GTK_FILL</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="left_attach">0</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                         <child>
+@@ -916,6 +885,7 @@
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+                             <property name="spacing">6</property>
++                            <property name="hexpand">true</property>
+                             <child>
+                               <object class="GtkEntry" id="entry_emoji_dialog">
+                                 <property name="visible">True</property>
+@@ -947,8 +917,7 @@
+                           </object>
+                           <packing>
+                             <property name="left_attach">1</property>
+-                            <property name="right_attach">2</property>
+-                            <property name="y_options">GTK_FILL</property>
++                            <property name="top_attach">0</property>
+                           </packing>
+                         </child>
+                       </object>
+-- 
+2.14.3
+
+From 5ee3f48049ecf128391da6448ae7e74786bd171b Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 18 Jun 2018 12:46:11 +0900
+Subject: [PATCH] Move input focus on Emojier to engines' preedit
+
+---
+ bindings/vala/IBus-1.0-custom.vala |   7 +
+ bindings/vala/Makefile.am          |   3 +-
+ bindings/vala/gdk-wayland.vapi     |   7 +
+ bus/engineproxy.c                  |  53 +-
+ bus/engineproxy.h                  |  25 +-
+ bus/ibusimpl.c                     | 247 +++++++--
+ bus/inputcontext.c                 | 399 +++++++++++----
+ bus/inputcontext.h                 | 110 +++-
+ bus/panelproxy.c                   | 210 +++++++-
+ bus/panelproxy.h                   |  23 +-
+ data/ibus.schemas.in               |  12 +
+ setup/main.py                      |  10 +-
+ setup/setup.ui                     |  58 ++-
+ src/ibusengine.c                   | 305 ++++++++----
+ src/ibuspanelservice.c             | 318 +++++++++++-
+ src/ibuspanelservice.h             | 117 ++++-
+ src/ibusshare.h                    |  17 +-
+ src/ibusxevent.c                   | 375 +++++++++++++-
+ src/ibusxevent.h                   | 143 +++++-
+ ui/gtk3/Makefile.am                |   3 +
+ ui/gtk3/emojier.vala               | 991 +++++++++++++++++++++++++++----------
+ ui/gtk3/emojierapp.vala            |  74 ++-
+ ui/gtk3/extension.vala             |   6 +-
+ ui/gtk3/panel.vala                 |  23 +-
+ ui/gtk3/panelbinding.vala          | 859 +++++++++++++++++++++++++++++---
+ 25 files changed, 3695 insertions(+), 700 deletions(-)
+ create mode 100644 bindings/vala/gdk-wayland.vapi
+
+diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala
+index cf1fc3fa..7d34a8bd 100644
+--- a/bindings/vala/IBus-1.0-custom.vala
++++ b/bindings/vala/IBus-1.0-custom.vala
+@@ -6,8 +6,15 @@ namespace IBus {
+ 		[CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)]
+ 		public Text.from_static_string (string str);
+ 	}
++	public class ExtensionEvent : IBus.Serializable {
++		[CCode (cname = "ibus_extension_event_new", has_construct_function = true)]
++		public ExtensionEvent (string first_property_name, ...);
++	}
+ 	public class XEvent : IBus.Serializable {
+ 		[CCode (cname = "ibus_x_event_new", has_construct_function = true)]
+ 		public XEvent (string first_property_name, ...);
+ 	}
++	public class PanelService : IBus.Service {
++                public void panel_extension_register_keys(string first_property_name, ...);
++	}
+ }
+diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am
+index fc8e2f01..e4ecab97 100644
+--- a/bindings/vala/Makefile.am
++++ b/bindings/vala/Makefile.am
+@@ -3,7 +3,7 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ # Copyright (c) 2007-2017 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+@@ -86,6 +86,7 @@ EXTRA_DIST =                                    \
+     ibus-1.0.deps                               \
+     ibus-emoji-dialog-1.0.deps                  \
+     config.vapi                                 \
++    gdk-wayland.vapi                            \
+     xi.vapi                                     \
+     $(NULL)
+ 
+diff --git a/bindings/vala/gdk-wayland.vapi b/bindings/vala/gdk-wayland.vapi
+new file mode 100644
+index 00000000..c65f2be4
+--- /dev/null
++++ b/bindings/vala/gdk-wayland.vapi
+@@ -0,0 +1,7 @@
++[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "gdk/gdkwayland.h")]
++namespace GdkWayland
++{
++    [CCode (type_id = "gdk_wayland_display_get_type ()")]
++    public class Display : Gdk.Display {
++    }
++}
+diff --git a/bus/engineproxy.c b/bus/engineproxy.c
+index 175aec56..2d98995c 100644
+--- a/bus/engineproxy.c
++++ b/bus/engineproxy.c
+@@ -377,10 +377,10 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class)
+             G_SIGNAL_RUN_LAST,
+             0,
+             NULL, NULL,
+-            bus_marshal_VOID__VARIANT,
++            bus_marshal_VOID__OBJECT,
+             G_TYPE_NONE,
+             1,
+-            G_TYPE_VARIANT);
++            IBUS_TYPE_EXTENSION_EVENT);
+ 
+     text_empty = ibus_text_new_from_static_string ("");
+     g_object_ref_sink (text_empty);
+@@ -644,7 +644,16 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
+     }
+ 
+     if (g_strcmp0 (signal_name, "PanelExtension") == 0) {
+-        g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, parameters);
++        GVariant *arg0 = NULL;
++        g_variant_get (parameters, "(v)", &arg0);
++        g_return_if_fail (arg0 != NULL);
++
++        IBusExtensionEvent *event = IBUS_EXTENSION_EVENT (
++                ibus_serializable_deserialize (arg0));
++        g_variant_unref (arg0);
++        g_return_if_fail (event != NULL);
++        g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, event);
++        _g_object_unref_if_floating (event);
+         return;
+     }
+ 
+@@ -1323,6 +1332,44 @@ bus_engine_proxy_is_enabled (BusEngineProxy *engine)
+     return engine->enabled;
+ }
+ 
++void
++bus_engine_proxy_panel_extension_received (BusEngineProxy     *engine,
++                                           IBusExtensionEvent *event)
++{
++    GVariant *variant;
++    g_assert (BUS_IS_ENGINE_PROXY (engine));
++    g_assert (IBUS_IS_EXTENSION_EVENT (event));
++
++    variant = ibus_serializable_serialize_object (
++            IBUS_SERIALIZABLE (event));
++    g_return_if_fail (variant != NULL);
++    g_dbus_proxy_call ((GDBusProxy *)engine,
++                       "PanelExtensionReceived",
++                       g_variant_new ("(v)", variant),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1,
++                       NULL,
++                       NULL,
++                       NULL);
++}
++
++void
++bus_engine_proxy_panel_extension_register_keys (BusEngineProxy *engine,
++                                                GVariant       *parameters)
++{
++    g_assert (BUS_IS_ENGINE_PROXY (engine));
++    g_assert (parameters);
++
++    g_dbus_proxy_call ((GDBusProxy *)engine,
++                       "PanelExtensionRegisterKeys",
++                       g_variant_new ("(v)", g_variant_ref (parameters)),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1,
++                       NULL,
++                       NULL,
++                       NULL);
++}
++
+ static gboolean
+ initable_init (GInitable     *initable,
+                GCancellable  *cancellable,
+diff --git a/bus/engineproxy.h b/bus/engineproxy.h
+index 528e61b7..a3006b47 100644
+--- a/bus/engineproxy.h
++++ b/bus/engineproxy.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.fujiwara@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
+@@ -325,5 +326,27 @@ void            bus_engine_proxy_set_content_type
+ IBusPropList   *bus_engine_proxy_get_properties
+                                              (BusEngineProxy     *engine);
+ 
++/**
++ * bus_engine_proxy_panel_extension_received:
++ * @engine: A #BusEngineProxy.
++ * @event: An #IBusExtensionEvent.
++ *
++ * Send an #IBusExtensionEvent to the engine.
++ */
++void            bus_engine_proxy_panel_extension_received
++                                             (BusEngineProxy     *engine,
++                                              IBusExtensionEvent *event);
++
++/**
++ * bus_engine_proxy_panel_extension_register_keys:
++ * @engine: A #BusEngineProxy.
++ * @parameters: A #GVariant array which includes the name and shortcut keys.
++ *
++ * Send shortcut keys to the engine to enable the extension.
++ */
++void            bus_engine_proxy_panel_extension_register_keys
++                                             (BusEngineProxy     *engine,
++                                              GVariant           *parameters);
++
+ G_END_DECLS
+ #endif
+diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
+index a4ce3d9d..ec1caea8 100644
+--- a/bus/ibusimpl.c
++++ b/bus/ibusimpl.c
+@@ -74,7 +74,8 @@ struct _BusIBusImpl {
+ 
+     BusInputContext *focused_context;
+     BusPanelProxy   *panel;
+-    BusPanelProxy   *extension;
++    BusPanelProxy   *emoji_extension;
++    gboolean         enable_emoji_extension;
+ 
+     /* a default keymap of ibus-daemon (usually "us") which is used only
+      * when use_sys_layout is FALSE. */
+@@ -83,6 +84,7 @@ struct _BusIBusImpl {
+     gboolean use_global_engine;
+     gchar *global_engine_name;
+     gchar *global_previous_engine_name;
++    GVariant *extension_register_keys;
+ };
+ 
+ struct _BusIBusImplClass {
+@@ -294,40 +296,158 @@ _panel_destroy_cb (BusPanelProxy *panel,
+ 
+     if (ibus->panel == panel)
+         ibus->panel = NULL;
+-    else if (ibus->extension == panel)
+-        ibus->extension = NULL;
++    else if (ibus->emoji_extension == panel)
++        ibus->emoji_extension = NULL;
+     else
+         g_return_if_reached ();
+     g_object_unref (panel);
+ }
+ 
+ static void
+-bus_ibus_impl_panel_extension_received (BusIBusImpl *ibus,
+-                                        GVariant    *parameters)
++bus_ibus_impl_set_panel_extension_mode (BusIBusImpl        *ibus,
++                                        IBusExtensionEvent *event)
+ {
+-    if (!ibus->extension) {
++    gboolean is_extension = FALSE;
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++    g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event));
++
++    if (!ibus->emoji_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));
++    g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->emoji_extension));
++
++    ibus->enable_emoji_extension = ibus_extension_event_is_enabled (event);
++    is_extension = ibus_extension_event_is_extension (event);
++    if (ibus->focused_context != NULL) {
++        if (ibus->enable_emoji_extension) {
++            bus_input_context_set_emoji_extension (ibus->focused_context,
++                                                   ibus->emoji_extension);
++        } else {
++            bus_input_context_set_emoji_extension (ibus->focused_context, NULL);
++        }
++        if (is_extension)
++            bus_input_context_panel_extension_received (ibus->focused_context,
++                                                        event);
++    }
++    if (is_extension)
++        return;
+ 
+     /* 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);
++    bus_panel_proxy_panel_extension_received (ibus->emoji_extension,
++                                              event);
++}
++
++static void
++bus_ibus_impl_set_panel_extension_keys (BusIBusImpl *ibus,
++                                        GVariant    *parameters)
++{
++    BusEngineProxy *engine = NULL;
++
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++    g_return_if_fail (parameters);
++
++    if (!ibus->emoji_extension) {
++        g_warning ("Panel extension is not running.");
++        return;
++    }
++
++    if (ibus->extension_register_keys)
++        g_variant_unref (ibus->extension_register_keys);
++    ibus->extension_register_keys = g_variant_ref_sink (parameters);
++    if (ibus->focused_context != NULL) {
++            engine = bus_input_context_get_engine (ibus->focused_context);
++    }
++    if (!engine)
++        return;
++    bus_engine_proxy_panel_extension_register_keys (engine, parameters);
+ }
+ 
+ static void
+-_panel_panel_extension_cb (BusPanelProxy *panel,
+-                           GVariant      *parameters,
+-                           BusIBusImpl  *ibus)
++_panel_panel_extension_cb (BusPanelProxy      *panel,
++                           IBusExtensionEvent *event,
++                           BusIBusImpl        *ibus)
+ {
+-    bus_ibus_impl_panel_extension_received (ibus, parameters);
++    bus_ibus_impl_set_panel_extension_mode (ibus, event);
++}
++
++static void
++_panel_panel_extension_register_keys_cb (BusInputContext *context,
++                                         GVariant        *parameters,
++                                         BusIBusImpl     *ibus)
++{
++    bus_ibus_impl_set_panel_extension_keys (ibus, parameters);
++}
++
++static void
++_panel_update_preedit_text_received_cb (BusPanelProxy *panel,
++                                        IBusText      *text,
++                                        guint          cursor_pos,
++                                        gboolean       visible,
++                                        BusIBusImpl   *ibus)
++{
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++
++    if (!ibus->focused_context)
++        return;
++    bus_input_context_update_preedit_text (ibus->focused_context,
++        text, cursor_pos, visible, IBUS_ENGINE_PREEDIT_CLEAR, FALSE);
++}
++
++static void
++_panel_update_lookup_table_received_cb (BusPanelProxy   *panel,
++                                        IBusLookupTable *table,
++                                        gboolean         visible,
++                                        BusIBusImpl     *ibus)
++{
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++    g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table));
++
++    if (!ibus->focused_context)
++        return;
++    /* Call bus_input_context_update_lookup_table() instead of
++     * bus_panel_proxy_update_lookup_table() for panel extensions because
++     * bus_input_context_page_up() can call bus_panel_proxy_page_up_received().
++     */
++    bus_input_context_update_lookup_table (
++            ibus->focused_context, table, visible, TRUE);
++}
++
++static void
++_panel_update_auxiliary_text_received_cb (BusPanelProxy *panel,
++                                          IBusText      *text,
++                                          gboolean       visible,
++                                          BusIBusImpl   *ibus)
++{
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++    g_return_if_fail (IBUS_IS_TEXT (text));
++
++    if (!ibus->panel)
++        return;
++    bus_panel_proxy_update_auxiliary_text (
++            ibus->panel, text, visible);
++}
++
++static void
++_panel_show_lookup_table_received_cb (BusPanelProxy *panel,
++                                      BusIBusImpl   *ibus)
++{
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++
++    if (ibus->panel)
++        bus_panel_proxy_show_lookup_table (ibus->panel);
++}
++
++static void
++_panel_hide_lookup_table_received_cb (BusPanelProxy *panel,
++                                      BusIBusImpl   *ibus)
++{
++    g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++
++    if (ibus->panel)
++        bus_panel_proxy_hide_lookup_table (ibus->panel);
+ }
+ 
+ static void
+@@ -361,8 +481,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+ 
+     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;
++    else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION_EMOJI))
++        panel_type = PANEL_TYPE_EXTENSION_EMOJI;
+ 
+     if (panel_type != PANEL_TYPE_NONE) {
+         if (g_strcmp0 (new_name, "") != 0) {
+@@ -370,7 +490,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+             BusConnection *connection;
+             BusInputContext *context = NULL;
+             BusPanelProxy   **panel = (panel_type == PANEL_TYPE_PANEL) ?
+-                                       &ibus->panel : &ibus->extension;
++                                      &ibus->panel : &ibus->emoji_extension;
+ 
+             if (*panel != NULL) {
+                 ibus_proxy_destroy ((IBusProxy *)(*panel));
+@@ -383,6 +503,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+             g_return_if_fail (connection != NULL);
+ 
+             *panel = bus_panel_proxy_new (connection, panel_type);
++            if (panel_type == PANEL_TYPE_EXTENSION_EMOJI)
++                ibus->enable_emoji_extension = FALSE;
+ 
+             g_signal_connect (*panel,
+                               "destroy",
+@@ -392,6 +514,26 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
+                               "panel-extension",
+                               G_CALLBACK (_panel_panel_extension_cb),
+                               ibus);
++            g_signal_connect (*panel,
++                              "panel-extension-register-keys",
++                              G_CALLBACK (
++                                      _panel_panel_extension_register_keys_cb),
++                              ibus);
++            g_signal_connect (
++                    *panel,
++                    "update-preedit-text-received",
++                    G_CALLBACK (_panel_update_preedit_text_received_cb),
++                    ibus);
++            g_signal_connect (
++                    *panel,
++                    "update-lookup-table-received",
++                    G_CALLBACK (_panel_update_lookup_table_received_cb),
++                    ibus);
++            g_signal_connect (
++                    *panel,
++                    "update-auxiliary-text-received",
++                    G_CALLBACK (_panel_update_auxiliary_text_received_cb),
++                    ibus);
+ 
+             if (ibus->focused_context != NULL) {
+                 context = ibus->focused_context;
+@@ -450,7 +592,7 @@ bus_ibus_impl_init (BusIBusImpl *ibus)
+     ibus->contexts = NULL;
+     ibus->focused_context = NULL;
+     ibus->panel = NULL;
+-    ibus->extension = NULL;
++    ibus->emoji_extension = NULL;
+ 
+     ibus->keymap = ibus_keymap_get ("us");
+ 
+@@ -650,11 +792,11 @@ bus_ibus_impl_set_context_engine_from_desc (BusIBusImpl     *ibus,
+ }
+ 
+ static void
+-_context_panel_extension_cb (BusInputContext *context,
+-                             GVariant        *parameters,
+-                             BusIBusImpl     *ibus)
++_context_panel_extension_cb (BusInputContext    *context,
++                             IBusExtensionEvent *event,
++                             BusIBusImpl        *ibus)
+ {
+-    bus_ibus_impl_panel_extension_received (ibus, parameters);
++    bus_ibus_impl_set_panel_extension_mode (ibus, event);
+ }
+ 
+ const static struct {
+@@ -694,13 +836,18 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
+             if (engine) {
+                 g_object_ref (engine);
+                 bus_input_context_set_engine (ibus->focused_context, NULL);
++                bus_input_context_set_emoji_extension (ibus->focused_context,
++                                                       NULL);
+             }
+         }
+ 
+         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);
++        if (ibus->emoji_extension != NULL) {
++            bus_panel_proxy_focus_out (ibus->emoji_extension,
++                                       ibus->focused_context);
++        }
++        bus_input_context_set_emoji_extension (ibus->focused_context, NULL);
+ 
+         bus_input_context_get_content_type (ibus->focused_context,
+                                             &purpose, &hints);
+@@ -724,6 +871,12 @@ bus_ibus_impl_set_focused_context (BusIBusImpl     *ibus,
+         if (engine != NULL) {
+             bus_input_context_set_engine (context, engine);
+             bus_input_context_enable (context);
++            if (ibus->enable_emoji_extension) {
++                bus_input_context_set_emoji_extension (context,
++                                                       ibus->emoji_extension);
++            } else {
++                bus_input_context_set_emoji_extension (context, NULL);
++            }
+         }
+         for (i = 0; i < G_N_ELEMENTS(context_signals); i++) {
+             g_signal_connect (ibus->focused_context,
+@@ -734,8 +887,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 (ibus->emoji_extension != NULL)
++            bus_panel_proxy_focus_in (ibus->emoji_extension, context);
+     }
+ 
+     if (engine != NULL)
+@@ -751,6 +904,12 @@ bus_ibus_impl_set_global_engine (BusIBusImpl    *ibus,
+ 
+     if (ibus->focused_context) {
+         bus_input_context_set_engine (ibus->focused_context, engine);
++        if (ibus->enable_emoji_extension) {
++            bus_input_context_set_emoji_extension (ibus->focused_context,
++                                                   ibus->emoji_extension);
++        } else {
++            bus_input_context_set_emoji_extension (ibus->focused_context, NULL);
++        }
+     } else if (ibus->fake_context) {
+         bus_input_context_set_engine (ibus->fake_context, engine);
+     }
+@@ -927,9 +1086,9 @@ _context_destroy_cb (BusInputContext    *context,
+         bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
+         bus_panel_proxy_destroy_context (ibus->panel, context);
+     }
+-    if (ibus->extension &&
++    if (ibus->emoji_extension &&
+         bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
+-        bus_panel_proxy_destroy_context (ibus->extension, context);
++        bus_panel_proxy_destroy_context (ibus->emoji_extension, context);
+     }
+ 
+     ibus->contexts = g_list_remove (ibus->contexts, context);
+@@ -1489,6 +1648,7 @@ _ibus_set_global_engine_ready_cb (BusInputContext       *context,
+     else {
+         g_dbus_method_invocation_return_value (data->invocation, NULL);
+ 
++        BusEngineProxy *engine = bus_input_context_get_engine (context);
+         if (ibus->use_global_engine && (context != ibus->focused_context)) {
+             /* context and ibus->focused_context don't match. This means that
+              * the focus is moved before _ibus_set_global_engine() asynchronous
+@@ -1496,14 +1656,28 @@ _ibus_set_global_engine_ready_cb (BusInputContext       *context,
+              * being focused hasn't been updated. Update the engine here so that
+              * subsequent _ibus_get_global_engine() call could return a
+              * consistent engine name. */
+-            BusEngineProxy *engine = bus_input_context_get_engine (context);
+             if (engine && ibus->focused_context != NULL) {
+                 g_object_ref (engine);
+                 bus_input_context_set_engine (context, NULL);
++                bus_input_context_set_emoji_extension (context, NULL);
+                 bus_input_context_set_engine (ibus->focused_context, engine);
++                if (ibus->enable_emoji_extension) {
++                    bus_input_context_set_emoji_extension (
++                            ibus->focused_context,
++                            ibus->emoji_extension);
++                } else {
++                    bus_input_context_set_emoji_extension (
++                            ibus->focused_context,
++                            NULL);
++                }
+                 g_object_unref (engine);
+             }
+         }
++        if (engine && ibus->extension_register_keys) {
++            bus_engine_proxy_panel_extension_register_keys (
++                    engine,
++                    ibus->extension_register_keys);
++        }
+     }
+ 
+     g_object_unref (ibus);
+@@ -2013,11 +2187,12 @@ bus_ibus_impl_registry_destroy (BusIBusImpl *ibus)
+     g_list_free_full (ibus->components, g_object_unref);
+     ibus->components = NULL;
+ 
+-    g_hash_table_destroy (ibus->engine_table);
+-    ibus->engine_table = NULL;
++    g_clear_pointer (&ibus->engine_table, g_hash_table_destroy);
+ 
+-    ibus_object_destroy (IBUS_OBJECT (ibus->registry));
+-    ibus->registry = NULL;
++    g_clear_pointer (&ibus->registry, ibus_object_destroy);
++
++    if (ibus->extension_register_keys)
++        g_clear_pointer (&ibus->extension_register_keys, g_variant_unref);
+ }
+ 
+ static gint
+diff --git a/bus/inputcontext.c b/bus/inputcontext.c
+index dfb98c36..bf9eafcf 100644
+--- a/bus/inputcontext.c
++++ b/bus/inputcontext.c
+@@ -94,6 +94,9 @@ struct _BusInputContext {
+     /* content-type (primary purpose and hints) */
+     guint    purpose;
+     guint    hints;
++
++    BusPanelProxy *emoji_extension;
++    gboolean is_extension_lookup_table;
+ };
+ 
+ struct _BusInputContextClass {
+@@ -162,16 +165,12 @@ static gboolean bus_input_context_service_set_property
+                                     GError               **error);
+ static void     bus_input_context_unset_engine
+                                    (BusInputContext       *context);
+-static void     bus_input_context_update_preedit_text
+-                                   (BusInputContext       *context,
+-                                    IBusText              *text,
+-                                    guint                  cursor_pos,
+-                                    gboolean               visible,
+-                                    guint                  mode);
+ static void     bus_input_context_show_preedit_text
+-                                   (BusInputContext       *context);
++                                   (BusInputContext       *context,
++                                    gboolean               is_extension);
+ static void     bus_input_context_hide_preedit_text
+-                                   (BusInputContext       *context);
++                                   (BusInputContext       *context,
++                                    gboolean               is_extension);
+ static void     bus_input_context_update_auxiliary_text
+                                    (BusInputContext       *context,
+                                     IBusText              *text,
+@@ -180,10 +179,6 @@ static void     bus_input_context_show_auxiliary_text
+                                    (BusInputContext       *context);
+ static void     bus_input_context_hide_auxiliary_text
+                                    (BusInputContext       *context);
+-static void     bus_input_context_update_lookup_table
+-                                   (BusInputContext       *context,
+-                                    IBusLookupTable       *table,
+-                                    gboolean               visible);
+ static void     bus_input_context_show_lookup_table
+                                    (BusInputContext       *context);
+ static void     bus_input_context_hide_lookup_table
+@@ -605,10 +600,10 @@ bus_input_context_class_init (BusInputContextClass *class)
+             G_SIGNAL_RUN_LAST,
+             0,
+             NULL, NULL,
+-            bus_marshal_VOID__VARIANT,
++            bus_marshal_VOID__OBJECT,
+             G_TYPE_NONE,
+             1,
+-            G_TYPE_VARIANT);
++            IBUS_TYPE_EXTENSION_EVENT);
+ 
+     text_empty = ibus_text_new_from_string ("");
+     g_object_ref_sink (text_empty);
+@@ -760,28 +755,85 @@ bus_input_context_property_changed (BusInputContext *context,
+                                           error);
+ }
+ 
++
++/**
++ * _panel_process_key_event_cb:
++ *
++ * A GAsyncReadyCallback function to be called when
++ * bus_panel_proxy_process_key_event() is finished.
++ */
++static void
++_panel_process_key_event_cb (GObject               *source,
++                             GAsyncResult          *res,
++                             GDBusMethodInvocation *invocation)
++{
++    GError *error = NULL;
++    GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source,
++                                                 res,
++                                                 &error);
++    if (value != NULL) {
++        g_dbus_method_invocation_return_value (invocation, value);
++        g_variant_unref (value);
++    }
++    else {
++        g_dbus_method_invocation_return_gerror (invocation, error);
++        g_error_free (error);
++    }
++}
++
++typedef struct _ProcessKeyEventData ProcessKeyEventData;
++struct _ProcessKeyEventData {
++    GDBusMethodInvocation *invocation;
++    BusInputContext       *context;
++    guint keyval;
++    guint keycode;
++    guint modifiers;
++};
++
+ /**
+  * _ic_process_key_event_reply_cb:
+  *
+- * A GAsyncReadyCallback function to be called when bus_engine_proxy_process_key_event() is finished.
++ * A GAsyncReadyCallback function to be called when
++ * bus_engine_proxy_process_key_event() is finished.
+  */
+ static void
+ _ic_process_key_event_reply_cb (GObject               *source,
+                                 GAsyncResult          *res,
+-                                GDBusMethodInvocation *invocation)
++                                ProcessKeyEventData   *data)
+ {
++    GDBusMethodInvocation *invocation = data->invocation;
++    BusInputContext *context = data->context;
++    guint keyval = data->keyval;
++    guint keycode = data->keycode;
++    guint modifiers = data->modifiers;
+     GError *error = NULL;
+     GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source,
+                                                  res,
+                                                  &error);
++
+     if (value != NULL) {
+-        g_dbus_method_invocation_return_value (invocation, value);
++        gboolean retval = FALSE;
++        g_variant_get (value, "(b)", &retval);
++        if (context->emoji_extension && !retval) {
++            bus_panel_proxy_process_key_event (context->emoji_extension,
++                                               keyval,
++                                               keycode,
++                                               modifiers,
++                                               (GAsyncReadyCallback)
++                                                    _panel_process_key_event_cb,
++                                               invocation);
++        } else {
++            g_dbus_method_invocation_return_value (invocation, value);
++        }
+         g_variant_unref (value);
+     }
+     else {
+         g_dbus_method_invocation_return_gerror (invocation, error);
+         g_error_free (error);
+     }
++
++    g_object_unref (context);
++    g_slice_free (ProcessKeyEventData, data);
+ }
+ 
+ /**
+@@ -840,12 +892,19 @@ _ic_process_key_event  (BusInputContext       *context,
+ 
+     /* ignore key events, if it is a fake input context */
+     if (context->has_focus && context->engine && context->fake == FALSE) {
++        ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData);
++        data->invocation = invocation;
++        data->context = g_object_ref (context);
++        data->keyval = keyval;
++        data->keycode = keycode;
++        data->modifiers = modifiers;
+         bus_engine_proxy_process_key_event (context->engine,
+                                             keyval,
+                                             keycode,
+                                             modifiers,
+-                                            (GAsyncReadyCallback) _ic_process_key_event_reply_cb,
+-                                            invocation);
++                                            (GAsyncReadyCallback)
++                                                _ic_process_key_event_reply_cb,
++                                            data);
+     }
+     else {
+         g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE));
+@@ -880,6 +939,13 @@ _ic_set_cursor_location (BusInputContext       *context,
+                        context->y,
+                        context->w,
+                        context->h);
++        if (context->emoji_extension) {
++            bus_panel_proxy_set_cursor_location (context->emoji_extension,
++                                                 context->x,
++                                                 context->y,
++                                                 context->w,
++                                                 context->h);
++        }
+     }
+ }
+ 
+@@ -912,6 +978,14 @@ _ic_set_cursor_location_relative (BusInputContext       *context,
+                        y,
+                        w,
+                        h);
++        if (context->emoji_extension) {
++            bus_panel_proxy_set_cursor_location_relative (
++                    context->emoji_extension,
++                    x,
++                    y,
++                    w,
++                    h);
++        }
+     }
+ }
+ 
+@@ -1394,7 +1468,7 @@ bus_input_context_clear_preedit_text (BusInputContext *context)
+ 
+     /* always clear preedit text */
+     bus_input_context_update_preedit_text (context,
+-        text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR);
++        text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR, TRUE);
+ }
+ 
+ void
+@@ -1407,7 +1481,10 @@ bus_input_context_focus_out (BusInputContext *context)
+ 
+     bus_input_context_clear_preedit_text (context);
+     bus_input_context_update_auxiliary_text (context, text_empty, FALSE);
+-    bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE);
++    bus_input_context_update_lookup_table (context,
++                                           lookup_table_empty,
++                                           FALSE,
++                                           FALSE);
+     bus_input_context_register_properties (context, props_empty);
+ 
+     if (context->engine) {
+@@ -1427,7 +1504,12 @@ bus_input_context_focus_out (BusInputContext *context)
+     {                                                                       \
+         g_assert (BUS_IS_INPUT_CONTEXT (context));                          \
+                                                                             \
+-        if (context->has_focus && context->engine) {    \
++        if (context->is_extension_lookup_table &&                           \
++            context->emoji_extension) {                                     \
++            bus_panel_proxy_##name##_lookup_table (context->emoji_extension); \
++            return;                                                         \
++        }                                                                   \
++        if (context->has_focus && context->engine) {                        \
+             bus_engine_proxy_##name (context->engine);                      \
+         }                                                                   \
+     }
+@@ -1447,6 +1529,14 @@ bus_input_context_candidate_clicked (BusInputContext *context,
+ {
+     g_assert (BUS_IS_INPUT_CONTEXT (context));
+ 
++    if (context->is_extension_lookup_table && context->emoji_extension) {
++        bus_panel_proxy_candidate_clicked_lookup_table (
++                context->emoji_extension,
++                index,
++                button,
++                state);
++            return;
++    }
+     if (context->engine) {
+         bus_engine_proxy_candidate_clicked (context->engine,
+                                             index,
+@@ -1467,61 +1557,33 @@ bus_input_context_property_activate (BusInputContext *context,
+     }
+ }
+ 
+-/**
+- * bus_input_context_update_preedit_text:
+- *
+- * Update a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client.
+- */
+-static void
+-bus_input_context_update_preedit_text (BusInputContext *context,
+-                                       IBusText        *text,
+-                                       guint            cursor_pos,
+-                                       gboolean         visible,
+-                                       guint            mode)
+-{
+-    g_assert (BUS_IS_INPUT_CONTEXT (context));
+-
+-    if (context->preedit_text) {
+-        g_object_unref (context->preedit_text);
+-    }
+-
+-    context->preedit_text = (IBusText *) g_object_ref_sink (text ? text : text_empty);
+-    context->preedit_cursor_pos = cursor_pos;
+-    context->preedit_visible = visible;
+-    context->preedit_mode = mode;
+-
+-    if (PREEDIT_CONDITION) {
+-        GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)context->preedit_text);
+-        bus_input_context_emit_signal (context,
+-                                       "UpdatePreeditText",
+-                                       g_variant_new ("(vub)", variant, context->preedit_cursor_pos, context->preedit_visible),
+-                                       NULL);
+-    }
+-    else {
+-        g_signal_emit (context,
+-                       context_signals[UPDATE_PREEDIT_TEXT],
+-                       0,
+-                       context->preedit_text,
+-                       context->preedit_cursor_pos,
+-                       context->preedit_visible);
+-    }
+-}
+-
+ /**
+  * bus_input_context_show_preedit_text:
+  *
+  * Show a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client.
+  */
+ static void
+-bus_input_context_show_preedit_text (BusInputContext *context)
++bus_input_context_show_preedit_text (BusInputContext *context,
++                                     gboolean         is_extension)
+ {
+     g_assert (BUS_IS_INPUT_CONTEXT (context));
+ 
+-    if (context->preedit_visible) {
++    if (context->preedit_visible)
+         return;
+-    }
++    if (!is_extension && context->emoji_extension)
++        return;
++
++    if (!is_extension)
++        context->preedit_visible = TRUE;
+ 
+-    context->preedit_visible = TRUE;
++    if (context->emoji_extension && !is_extension) {
++        /* Do not use HIDE_PREEDIT_TEXT signal below but call
++         * bus_panel_proxy_hide_preedit_text() directly for the extension only
++         * but not for the normal panel.
++         */
++        bus_panel_proxy_show_preedit_text (context->emoji_extension);
++        return;
++    }
+ 
+     if (PREEDIT_CONDITION) {
+         bus_input_context_emit_signal (context,
+@@ -1542,15 +1604,25 @@ bus_input_context_show_preedit_text (BusInputContext *context)
+  * Hide a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client.
+  */
+ static void
+-bus_input_context_hide_preedit_text (BusInputContext *context)
++bus_input_context_hide_preedit_text (BusInputContext *context,
++                                     gboolean         is_extension)
+ {
+     g_assert (BUS_IS_INPUT_CONTEXT (context));
+ 
+-    if (!context->preedit_visible) {
++    if (!is_extension && !context->preedit_visible)
+         return;
+-    }
+ 
+-    context->preedit_visible = FALSE;
++    if (!is_extension)
++        context->preedit_visible = FALSE;
++
++    if (context->emoji_extension && !is_extension) {
++        /* Do not use HIDE_PREEDIT_TEXT signal below but call
++         * bus_panel_proxy_hide_preedit_text() directly for the extension only
++         * but not for the normal panel.
++         */
++        bus_panel_proxy_hide_preedit_text (context->emoji_extension);
++        return;
++    }
+ 
+     if (PREEDIT_CONDITION) {
+         bus_input_context_emit_signal (context,
+@@ -1658,19 +1730,15 @@ bus_input_context_hide_auxiliary_text (BusInputContext *context)
+     }
+ }
+ 
+-/**
+- * bus_input_context_update_lookup_table:
+- *
+- * Update contents in the lookup table.
+- * Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client.
+- */
+-static void
++void
+ bus_input_context_update_lookup_table (BusInputContext *context,
+                                        IBusLookupTable *table,
+-                                       gboolean         visible)
++                                       gboolean         visible,
++                                       gboolean         is_extension)
+ {
+     g_assert (BUS_IS_INPUT_CONTEXT (context));
+ 
++    context->is_extension_lookup_table = is_extension;
+     if (context->lookup_table) {
+         g_object_unref (context->lookup_table);
+     }
+@@ -2035,7 +2103,9 @@ _engine_update_preedit_text_cb (BusEngineProxy  *engine,
+ 
+     g_assert (context->engine == engine);
+ 
+-    bus_input_context_update_preedit_text (context, text, cursor_pos, visible, mode);
++    bus_input_context_update_preedit_text (context, text,
++                                           cursor_pos, visible, mode,
++                                           TRUE);
+ }
+ 
+ /**
+@@ -2075,7 +2145,7 @@ _engine_update_lookup_table_cb (BusEngineProxy   *engine,
+ 
+     g_assert (context->engine == engine);
+ 
+-    bus_input_context_update_lookup_table (context, table, visible);
++    bus_input_context_update_lookup_table (context, table, visible, FALSE);
+ }
+ 
+ /**
+@@ -2123,11 +2193,35 @@ _engine_update_property_cb (BusEngineProxy  *engine,
+  * from the engine object.
+  */
+ static void
+-_engine_panel_extension_cb (BusEngineProxy  *engine,
+-                            GVariant        *parameters,
+-                            BusInputContext *context)
++_engine_panel_extension_cb (BusEngineProxy     *engine,
++                            IBusExtensionEvent *event,
++                            BusInputContext    *context)
+ {
+-    g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, parameters);
++    g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, event);
++}
++
++static void
++_engine_show_preedit_text_cb (BusEngineProxy  *engine,
++                              BusInputContext *context)
++{
++    g_assert (BUS_IS_ENGINE_PROXY (engine));
++    g_assert (BUS_IS_INPUT_CONTEXT (context));
++
++    g_assert (context->engine == engine);
++
++    bus_input_context_show_preedit_text (context, FALSE);
++}
++
++static void
++_engine_hide_preedit_text_cb (BusEngineProxy  *engine,
++                              BusInputContext *context)
++{
++    g_assert (BUS_IS_ENGINE_PROXY (engine));
++    g_assert (BUS_IS_INPUT_CONTEXT (context));
++
++    g_assert (context->engine == engine);
++
++    bus_input_context_hide_preedit_text (context, FALSE);
+ }
+ 
+ #define DEFINE_FUNCTION(name)                                   \
+@@ -2143,8 +2237,6 @@ _engine_panel_extension_cb (BusEngineProxy  *engine,
+         bus_input_context_##name (context);                     \
+     }
+ 
+-DEFINE_FUNCTION (show_preedit_text)
+-DEFINE_FUNCTION (hide_preedit_text)
+ DEFINE_FUNCTION (show_auxiliary_text)
+ DEFINE_FUNCTION (hide_auxiliary_text)
+ DEFINE_FUNCTION (show_lookup_table)
+@@ -2239,7 +2331,10 @@ bus_input_context_disable (BusInputContext *context)
+ 
+     bus_input_context_clear_preedit_text (context);
+     bus_input_context_update_auxiliary_text (context, text_empty, FALSE);
+-    bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE);
++    bus_input_context_update_lookup_table (context,
++                                           lookup_table_empty,
++                                           FALSE,
++                                           FALSE);
+     bus_input_context_register_properties (context, props_empty);
+ 
+     if (context->engine) {
+@@ -2283,7 +2378,10 @@ bus_input_context_unset_engine (BusInputContext *context)
+ 
+     bus_input_context_clear_preedit_text (context);
+     bus_input_context_update_auxiliary_text (context, text_empty, FALSE);
+-    bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE);
++    bus_input_context_update_lookup_table (context,
++                                           lookup_table_empty,
++                                           FALSE,
++                                           FALSE);
+     bus_input_context_register_properties (context, props_empty);
+ 
+     if (context->engine) {
+@@ -2639,17 +2737,128 @@ bus_input_context_set_content_type (BusInputContext *context,
+ }
+ 
+ void
+-bus_input_context_commit_text (BusInputContext *context,
+-                               IBusText        *text)
++bus_input_context_commit_text_use_extension (BusInputContext *context,
++                                             IBusText        *text,
++                                             gboolean         use_extension)
+ {
+     g_assert (BUS_IS_INPUT_CONTEXT (context));
+ 
+     if (text == text_empty || text == NULL)
+         return;
+ 
+-    GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text);
+-    bus_input_context_emit_signal (context,
+-                                   "CommitText",
+-                                   g_variant_new ("(v)", variant),
+-                                   NULL);
++    if (use_extension && context->emoji_extension) {
++        bus_panel_proxy_commit_text_received (context->emoji_extension, text);
++    } else {
++        GVariant *variant = ibus_serializable_serialize (
++                (IBusSerializable *)text);
++        bus_input_context_emit_signal (context,
++                                       "CommitText",
++                                       g_variant_new ("(v)", variant),
++                                       NULL);
++    }
++}
++
++void
++bus_input_context_commit_text (BusInputContext *context,
++                               IBusText        *text)
++{
++    bus_input_context_commit_text_use_extension (context, text, TRUE);
++}
++
++void
++bus_input_context_update_preedit_text (BusInputContext *context,
++                                       IBusText        *text,
++                                       guint            cursor_pos,
++                                       gboolean         visible,
++                                       guint            mode,
++                                       gboolean         use_extension)
++{
++    gboolean extension_visible = FALSE;
++    g_assert (BUS_IS_INPUT_CONTEXT (context));
++
++    if (context->preedit_text) {
++        g_object_unref (context->preedit_text);
++    }
++
++    context->preedit_text = (IBusText *) g_object_ref_sink (text ? text :
++                                                            text_empty);
++    context->preedit_cursor_pos = cursor_pos;
++    if (use_extension)
++        context->preedit_visible = visible;
++    if (use_extension)
++        context->preedit_mode = mode;
++    extension_visible = context->preedit_visible |
++                        (context->emoji_extension != NULL);
++
++    if (use_extension && context->emoji_extension) {
++        bus_panel_proxy_update_preedit_text (context->emoji_extension,
++                                             context->preedit_text,
++                                             context->preedit_cursor_pos,
++                                             context->preedit_visible);
++    } else if (PREEDIT_CONDITION) {
++        GVariant *variant = ibus_serializable_serialize (
++                (IBusSerializable *)context->preedit_text);
++        bus_input_context_emit_signal (context,
++                                       "UpdatePreeditText",
++                                       g_variant_new (
++                                               "(vub)",
++                                               variant,
++                                               context->preedit_cursor_pos,
++                                               extension_visible),
++                                       NULL);
++    } else {
++        g_signal_emit (context,
++                       context_signals[UPDATE_PREEDIT_TEXT],
++                       0,
++                       context->preedit_text,
++                       context->preedit_cursor_pos,
++                       extension_visible);
++    }
++}
++
++void
++bus_input_context_set_emoji_extension (BusInputContext *context,
++                                       BusPanelProxy   *emoji_extension)
++{
++    g_assert (BUS_IS_INPUT_CONTEXT (context));
++
++    if (context->emoji_extension)
++        g_object_unref (context->emoji_extension);
++    context->emoji_extension = emoji_extension;
++    if (emoji_extension) {
++        g_object_ref (context->emoji_extension);
++        if (!context->connection)
++            return;
++        bus_input_context_show_preedit_text (context, TRUE);
++        bus_panel_proxy_set_cursor_location (context->emoji_extension,
++                                             context->x,
++                                             context->y,
++                                             context->w,
++                                             context->h);
++    } else {
++        if (!context->connection)
++            return;
++        /* https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/113
++         * Cannot use bus_input_context_hide_preedit_text () yet.
++         */
++        if (!context->preedit_visible) {
++            bus_input_context_update_preedit_text (context,
++                                                   text_empty,
++                                                   0,
++                                                   FALSE,
++                                                   IBUS_ENGINE_PREEDIT_CLEAR,
++                                                   FALSE);
++        }
++    }
++}
++
++void
++bus_input_context_panel_extension_received (BusInputContext    *context,
++                                            IBusExtensionEvent *event)
++{
++    g_assert (BUS_IS_INPUT_CONTEXT (context));
++
++    if (!context->engine)
++        return;
++    bus_engine_proxy_panel_extension_received (context->engine, event);
+ }
+diff --git a/bus/inputcontext.h b/bus/inputcontext.h
+index 7674abd8..a46d5c06 100644
+--- a/bus/inputcontext.h
++++ b/bus/inputcontext.h
+@@ -28,6 +28,11 @@
+ #include "connection.h"
+ #include "factoryproxy.h"
+ 
++#ifndef __BUS_PANEL_PROXY_DEFINED
++#define __BUS_PANEL_PROXY_DEFINED
++typedef struct _BusPanelProxy BusPanelProxy;
++#endif
++
+ /*
+  * Type macros.
+  */
+@@ -63,6 +68,7 @@ BusInputContext     *bus_input_context_new      (BusConnection      *connection,
+ 
+ /**
+  * bus_input_context_focus_in:
++ * @context: A #BusInputContext.
+  *
+  * Give a focus to the context. Call FocusIn, Enable, SetCapabilities,
+  * and SetCursorLocation methods of the engine for the context,
+@@ -73,6 +79,7 @@ void                 bus_input_context_focus_in (BusInputContext    *context);
+ 
+ /**
+  * bus_input_context_focus_out:
++ * @context: A #BusInputContext.
+  *
+  * Remove a focus from the context. Call FocusOut method of the engine for
+  * the context.
+@@ -83,6 +90,7 @@ void                 bus_input_context_focus_out
+ 
+ /**
+  * bus_input_context_has_focus:
++ * @context: A #BusInputContext.
+  * @returns: context->has_focus.
+  */
+ gboolean             bus_input_context_has_focus
+@@ -90,6 +98,7 @@ gboolean             bus_input_context_has_focus
+ 
+ /**
+  * bus_input_context_enable:
++ * @context: A #BusInputContext.
+  *
+  * Enable the current engine for the context. Request an engine (if needed),
+  * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods
+@@ -100,6 +109,7 @@ void                 bus_input_context_enable   (BusInputContext    *context);
+ 
+ /**
+  * bus_input_context_disable:
++ * @context: A #BusInputContext.
+  *
+  * Disable the current engine for the context. Request an engine (if needed),
+  * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods
+@@ -110,6 +120,7 @@ void                 bus_input_context_disable  (BusInputContext    *context);
+ 
+ /**
+  * bus_input_context_page_up:
++ * @context: A #BusInputContext.
+  *
+  * Call page_up method of the current engine proxy.
+  */
+@@ -117,6 +128,7 @@ void                 bus_input_context_page_up  (BusInputContext    *context);
+ 
+ /**
+  * bus_input_context_page_down:
++ * @context: A #BusInputContext.
+  *
+  * Call page_down method of the current engine proxy.
+  */
+@@ -125,6 +137,7 @@ void                 bus_input_context_page_down
+ 
+ /**
+  * bus_input_context_cursor_up:
++ * @context: A #BusInputContext.
+  *
+  * Call cursor_up method of the current engine proxy.
+  */
+@@ -133,6 +146,7 @@ void                 bus_input_context_cursor_up
+ 
+ /**
+  * bus_input_context_cursor_down:
++ * @context: A #BusInputContext.
+  *
+  * Call cursor_down method of the current engine proxy.
+  */
+@@ -141,6 +155,10 @@ void                 bus_input_context_cursor_down
+ 
+ /**
+  * bus_input_context_candidate_clicked:
++ * @context: A #BusInputContext.
++ * @index: An index.
++ * @button: A button number.
++ * @state: A button state.
+  *
+  * Call candidate_clicked method of the current engine proxy.
+  */
+@@ -152,6 +170,8 @@ void                 bus_input_context_candidate_clicked
+ 
+ /**
+  * bus_input_context_set_engine:
++ * @context: A #BusInputContext.
++ * @engine: A #BusEngineProxy.
+  *
+  * Use the engine on the context.
+  */
+@@ -161,12 +181,14 @@ void                 bus_input_context_set_engine
+ 
+ /**
+  * bus_input_context_set_engine_by_desc:
++ * @context: A #BusInputContext.
+  * @desc: the engine to use on the context.
+  * @timeout: timeout (in ms) for D-Bus calls.
+  * @callback: a function to be called when bus_input_context_set_engine_by_desc
+  *            is finished. if NULL, the default callback
+  *            function, which just calls
+  *            bus_input_context_set_engine_by_desc_finish, is used.
++ * @user_data: an argument of @callback.
+  *
+  * Create a new BusEngineProxy object and use it on the context.
+  */
+@@ -181,6 +203,9 @@ void                 bus_input_context_set_engine_by_desc
+ 
+ /**
+  * bus_input_context_set_engine_by_desc_finish:
++ * @context: A #BusInputContext.
++ * @res: A #GAsyncResult.
++ * @error: A #GError.
+  *
+  * A function to be called by the GAsyncReadyCallback function for
+  * bus_input_context_set_engine_by_desc.
+@@ -192,6 +217,7 @@ gboolean             bus_input_context_set_engine_by_desc_finish
+ 
+ /**
+  * bus_input_context_get_engine:
++ * @context: A #BusInputContext.
+  *
+  * Get a BusEngineProxy object of the current engine.
+  */
+@@ -200,6 +226,7 @@ BusEngineProxy      *bus_input_context_get_engine
+ 
+ /**
+  * bus_input_context_get_engine_desc:
++ * @context: A #BusInputContext.
+  *
+  * Get an IBusEngineDesc object of the current engine.
+  */
+@@ -208,6 +235,9 @@ IBusEngineDesc      *bus_input_context_get_engine_desc
+ 
+ /**
+  * bus_input_context_property_activate:
++ * @context: A #BusInputContext.
++ * @prop_name: A property name.
++ * @prop_state: A property state.
+  *
+  * Call property_activate method of the current engine proxy.
+  */
+@@ -219,6 +249,7 @@ void                 bus_input_context_property_activate
+ 
+ /**
+  * bus_input_context_get_capabilities:
++ * @context: A #BusInputContext.
+  * @returns: context->capabilities.
+  */
+ guint                bus_input_context_get_capabilities
+@@ -226,6 +257,8 @@ guint                bus_input_context_get_capabilities
+ 
+ /**
+  * bus_input_context_set_capabilities:
++ * @context: A #BusInputContext.
++ * @capabilities: capabilities.
+  *
+  * Call set_capabilities method of the current engine proxy.
+  */
+@@ -236,6 +269,7 @@ void                 bus_input_context_set_capabilities
+ 
+ /**
+  * bus_input_context_get_client:
++ * @context: A #BusInputContext.
+  * @returns: context->client.
+  */
+ const gchar         *bus_input_context_get_client
+@@ -243,6 +277,7 @@ const gchar         *bus_input_context_get_client
+ 
+ /**
+  * bus_input_context_get_content_type:
++ * @context: A #BusInputContext.
+  * @purpose: Input purpose.
+  * @hints: Input hints.
+  */
+@@ -253,6 +288,7 @@ void                 bus_input_context_get_content_type
+ 
+ /**
+  * bus_input_context_set_content_type:
++ * @context: A #BusInputContext.
+  * @purpose: Input purpose.
+  * @hints: Input hints.
+  */
+@@ -263,11 +299,83 @@ void                 bus_input_context_set_content_type
+ 
+ /**
+  * bus_input_context_commit_text:
+- * @text: a commited text.
++ * @context: A #BusInputContext.
++ * @text: A committed text.
+  */
+ void                 bus_input_context_commit_text
+                                                 (BusInputContext *context,
+                                                  IBusText        *text);
+ 
++/**
++ * bus_input_context_commit_text:
++ * @context: A #BusInputContext.
++ * @text: A committed text.
++ * @use_extension: Use an extension if it's %TRUE and the extension is
++ *                 available.
++ */
++void                 bus_input_context_commit_text_use_extension
++                                               (BusInputContext *context,
++                                                IBusText        *text,
++                                                gboolean         use_extension);
++
++/**
++ * bus_input_context_set_emoji_extension:
++ * @context: A #BusInputContext.
++ * @extension: A #BusPanelProxy.
++ */
++void                 bus_input_context_set_emoji_extension
++                                                (BusInputContext *context,
++                                                 BusPanelProxy   *extension);
++
++/**
++ * bus_input_context_update_preedit_text:
++ * @context: A #BusInputContext.
++ * @text: An #IBusText.
++ * @cursor_pos: The cursor position.
++ * @visible: %TRUE if the preedit is visible. Otherwise %FALSE.
++ * @mode: The preedit commit mode.
++ * @use_extension: %TRUE if preedit text is sent to the extesion at first.
++ *
++ * Update a preedit text. Send D-Bus signal to update status of client or
++ * send glib signal to the panel, depending on capabilities of the client.
++ */
++void                 bus_input_context_update_preedit_text
++                                                (BusInputContext    *context,
++                                                 IBusText           *text,
++                                                 guint               cursor_pos,
++                                                 gboolean            visible,
++                                                 guint               mode,
++                                                 gboolean
++                                                                 use_extension);
++
++/**
++ * bus_input_context_update_lookup_table:
++ * @context: A #BusInputContext.
++ * @table: An #IBusTable.
++ * @visible: %TRUE if the lookup table is visible. Otherwise %FALSE.
++ * @is_extension: %TRUE if the lookup table is created by panel extensions.
++ *
++ * Update contents in the lookup table.
++ * Send D-Bus signal to update status of client or send glib signal to the
++ * panel, depending on capabilities of the client.
++ */
++void                 bus_input_context_update_lookup_table
++                                                (BusInputContext    *context,
++                                                 IBusLookupTable    *table,
++                                                 gboolean            visible,
++                                                 gboolean
++                                                                  is_extension);
++
++
++/**
++ * bus_input_context_panel_extension_received:
++ * @context: A #BusInputContext.
++ * @event: An #IBusExtensionEvent.
++ *
++ * Send An #IBusExtensionEvent callback from an extension.
++ */
++void                 bus_input_context_panel_extension_received
++                                                (BusInputContext    *context,
++                                                 IBusExtensionEvent *event);
+ G_END_DECLS
+ #endif
+diff --git a/bus/panelproxy.c b/bus/panelproxy.c
+index c3908fcf..1c0fcca2 100644
+--- a/bus/panelproxy.c
++++ b/bus/panelproxy.c
+@@ -52,6 +52,10 @@ enum {
+     PROPERTY_HIDE,
+     COMMIT_TEXT,
+     PANEL_EXTENSION,
++    PANEL_EXTENSION_REGISTER_KEYS,
++    UPDATE_PREEDIT_TEXT_RECEIVED,
++    UPDATE_LOOKUP_TABLE_RECEIVED,
++    UPDATE_AUXILIARY_TEXT_RECEIVED,
+     LAST_SIGNAL,
+ };
+ 
+@@ -125,8 +129,8 @@ bus_panel_proxy_new (BusConnection *connection,
+     case PANEL_TYPE_PANEL:
+         path = IBUS_PATH_PANEL;
+         break;
+-    case PANEL_TYPE_EXTENSION:
+-        path = IBUS_PATH_PANEL_EXTENSION;
++    case PANEL_TYPE_EXTENSION_EMOJI:
++        path = IBUS_PATH_PANEL_EXTENSION_EMOJI;
+         break;
+     default:
+         g_return_val_if_reached (NULL);
+@@ -253,6 +257,16 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class)
+ 
+     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__OBJECT,
++            G_TYPE_NONE, 1,
++            IBUS_TYPE_EXTENSION_EVENT);
++
++    panel_signals[PANEL_EXTENSION_REGISTER_KEYS] =
++        g_signal_new (I_("panel-extension-register-keys"),
+             G_TYPE_FROM_CLASS (class),
+             G_SIGNAL_RUN_LAST,
+             0,
+@@ -260,6 +274,40 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class)
+             bus_marshal_VOID__VARIANT,
+             G_TYPE_NONE, 1,
+             G_TYPE_VARIANT);
++
++    panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED] =
++        g_signal_new (I_("update-preedit-text-received"),
++            G_TYPE_FROM_CLASS (class),
++            G_SIGNAL_RUN_LAST,
++            0,
++            NULL, NULL,
++            bus_marshal_VOID__OBJECT_UINT_BOOLEAN,
++            G_TYPE_NONE, 3,
++            IBUS_TYPE_TEXT,
++            G_TYPE_UINT,
++            G_TYPE_BOOLEAN);
++
++    panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED] =
++        g_signal_new (I_("update-lookup-table-received"),
++            G_TYPE_FROM_CLASS (class),
++            G_SIGNAL_RUN_LAST,
++            0,
++            NULL, NULL,
++            bus_marshal_VOID__OBJECT_BOOLEAN,
++            G_TYPE_NONE, 2,
++            IBUS_TYPE_LOOKUP_TABLE,
++            G_TYPE_BOOLEAN);
++
++    panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED] =
++        g_signal_new (I_("update-auxiliary-text-received"),
++            G_TYPE_FROM_CLASS (class),
++            G_SIGNAL_RUN_LAST,
++            0,
++            NULL, NULL,
++            bus_marshal_VOID__OBJECT_BOOLEAN,
++            G_TYPE_NONE, 2,
++            IBUS_TYPE_TEXT,
++            G_TYPE_BOOLEAN);
+ }
+ 
+ static void
+@@ -355,23 +403,83 @@ bus_panel_proxy_g_signal (GDBusProxy  *proxy,
+ 
+     if (g_strcmp0 ("CommitText", signal_name) == 0) {
+         GVariant *arg0 = NULL;
+-        g_variant_get (parameters, "(v)", &arg0);
+-        g_return_if_fail (arg0 != NULL);
+ 
++        g_variant_get (parameters, "(v)", &arg0);
++        g_return_if_fail (arg0);
+         IBusText *text = IBUS_TEXT (ibus_serializable_deserialize (arg0));
+         g_variant_unref (arg0);
+-        g_return_if_fail (text != NULL);
++        g_return_if_fail (text);
+         g_signal_emit (panel, panel_signals[COMMIT_TEXT], 0, text);
+         _g_object_unref_if_floating (text);
+         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);
++        GVariant *arg0 = NULL;
++
++        g_variant_get (parameters, "(v)", &arg0);
++        g_return_if_fail (arg0);
++        IBusExtensionEvent *event = IBUS_EXTENSION_EVENT (
++                ibus_serializable_deserialize (arg0));
++        g_variant_unref (arg0);
++        g_return_if_fail (event);
++        g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, event);
++        _g_object_unref_if_floating (event);
++        return;
++    }
++
++    if (g_strcmp0 ("PanelExtensionRegisterKeys", signal_name) == 0) {
++        g_signal_emit (panel, panel_signals[PANEL_EXTENSION_REGISTER_KEYS], 0,
++                       parameters);
++        return;
++    }
++
++    if (g_strcmp0 ("UpdatePreeditTextReceived", signal_name) == 0) {
++        GVariant *variant = NULL;
++        guint cursor_pos = 0;
++        gboolean visible = FALSE;
++        IBusText *text = NULL;
++
++        g_variant_get (parameters, "(vub)", &variant, &cursor_pos, &visible);
++        g_return_if_fail (variant);
++        text = (IBusText *) ibus_serializable_deserialize (variant);
++        g_variant_unref (variant);
++        g_return_if_fail (text);
++        g_signal_emit (panel, panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED], 0,
++                       text, cursor_pos, visible);
++        _g_object_unref_if_floating (text);
++        return;
++    }
++
++    if (g_strcmp0 ("UpdateLookupTableReceived", signal_name) == 0) {
++        GVariant *variant = NULL;
++        gboolean visible = FALSE;
++        IBusLookupTable *table = NULL;
++
++        g_variant_get (parameters, "(vb)", &variant, &visible);
++        g_return_if_fail (variant);
++        table = (IBusLookupTable *) ibus_serializable_deserialize (variant);
++        g_variant_unref (variant);
++        g_return_if_fail (table);
++        g_signal_emit (panel, panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED], 0,
++                       table, visible);
++        _g_object_unref_if_floating (table);
++        return;
++    }
++
++    if (g_strcmp0 ("UpdateAuxiliaryTextReceived", signal_name) == 0) {
++        GVariant *variant = NULL;
++        gboolean visible = FALSE;
++        IBusText *text = NULL;
++
++        g_variant_get (parameters, "(vb)", &variant, &visible);
++        g_return_if_fail (variant);
++        text = (IBusText *) ibus_serializable_deserialize (variant);
++        g_variant_unref (variant);
++        g_return_if_fail (text);
++        g_signal_emit (panel, panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED], 0,
++                       text, visible);
++        _g_object_unref_if_floating (text);
+         return;
+     }
+ 
+@@ -552,12 +660,17 @@ static void
+ bus_panel_proxy_commit_text (BusPanelProxy *panel,
+                              IBusText      *text)
+ {
++    gboolean use_extension = TRUE;
+     g_assert (BUS_IS_PANEL_PROXY (panel));
+     g_assert (text != NULL);
+ 
+-    if (panel->focused_context) {
+-        bus_input_context_commit_text (panel->focused_context, text);
+-    }
++    if (!panel->focused_context)
++        return;
++    if (panel->panel_type != PANEL_TYPE_PANEL)
++        use_extension = FALSE;
++    bus_input_context_commit_text_use_extension (panel->focused_context,
++                                                 text,
++                                                 use_extension);
+ }
+ 
+ #define DEFINE_FUNCTION(Name, name)                     \
+@@ -877,3 +990,74 @@ bus_panel_proxy_get_panel_type (BusPanelProxy    *panel)
+     g_assert (BUS_IS_PANEL_PROXY (panel));
+     return panel->panel_type;
+ }
++
++void
++bus_panel_proxy_panel_extension_received (BusPanelProxy      *panel,
++                                          IBusExtensionEvent *event)
++{
++    GVariant *data;
++
++    g_assert (BUS_IS_PANEL_PROXY (panel));
++    g_assert (event);
++
++    data = ibus_serializable_serialize (IBUS_SERIALIZABLE (event));
++    g_return_if_fail (data);
++    g_dbus_proxy_call ((GDBusProxy *)panel,
++                       "PanelExtensionReceived",
++                       g_variant_new ("(v)", data),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1, NULL, NULL, NULL);
++}
++
++void
++bus_panel_proxy_process_key_event (BusPanelProxy       *panel,
++                                   guint                keyval,
++                                   guint                keycode,
++                                   guint                state,
++                                   GAsyncReadyCallback  callback,
++                                   gpointer             user_data)
++{
++    g_assert (BUS_IS_PANEL_PROXY (panel));
++
++    g_dbus_proxy_call ((GDBusProxy *)panel,
++                       "ProcessKeyEvent",
++                       g_variant_new ("(uuu)", keyval, keycode, state),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1,
++                       NULL,
++                       callback,
++                       user_data);
++}
++
++void
++bus_panel_proxy_commit_text_received (BusPanelProxy *panel,
++                                      IBusText      *text)
++{
++    GVariant *variant;
++
++    g_assert (BUS_IS_PANEL_PROXY (panel));
++    g_assert (IBUS_IS_TEXT (text));
++
++    variant = ibus_serializable_serialize (IBUS_SERIALIZABLE (text));
++    g_dbus_proxy_call ((GDBusProxy *)panel,
++                       "CommitTextReceived",
++                       g_variant_new ("(v)", variant),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1, NULL, NULL, NULL);
++}
++
++void
++bus_panel_proxy_candidate_clicked_lookup_table (BusPanelProxy *panel,
++                                                guint          index,
++                                                guint          button,
++                                                guint          state)
++{
++    gboolean use_extension = TRUE;
++    g_assert (BUS_IS_PANEL_PROXY (panel));
++
++    g_dbus_proxy_call ((GDBusProxy *)panel,
++                       "CandidateClickedLookupTable",
++                       g_variant_new ("(uuu)", index, button, state),
++                       G_DBUS_CALL_FLAGS_NONE,
++                       -1, NULL, NULL, NULL);
++}
+diff --git a/bus/panelproxy.h b/bus/panelproxy.h
+index b5a7af17..4d8afb98 100644
+--- a/bus/panelproxy.h
++++ b/bus/panelproxy.h
+@@ -55,7 +55,7 @@ typedef enum
+ {
+     PANEL_TYPE_NONE,
+     PANEL_TYPE_PANEL,
+-    PANEL_TYPE_EXTENSION
++    PANEL_TYPE_EXTENSION_EMOJI
+ } PanelType;
+ 
+ typedef struct _BusPanelProxy BusPanelProxy;
+@@ -135,6 +135,27 @@ void             bus_panel_proxy_set_content_type
+                                                 guint              hints);
+ PanelType        bus_panel_proxy_get_panel_type
+                                                (BusPanelProxy     *panel);
++void             bus_panel_proxy_panel_extension_received
++                                               (BusPanelProxy     *panel,
++                                                IBusExtensionEvent
++                                                                  *event);
++void             bus_panel_proxy_process_key_event
++                                               (BusPanelProxy     *panel,
++                                                guint              keyval,
++                                                guint              keycode,
++                                                guint              state,
++                                                GAsyncReadyCallback
++                                                                   callback,
++                                                gpointer           user_data);
++void             bus_panel_proxy_commit_text_received
++                                               (BusPanelProxy     *panel,
++                                                IBusText          *text);
++void             bus_panel_proxy_candidate_clicked_lookup_table
++                                               (BusPanelProxy     *panel,
++                                                guint              index,
++                                                guint              button,
++                                                guint              state);
++
+ G_END_DECLS
+ #endif
+ 
+diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
+index 3c6b6f69..f4a019d0 100644
+--- a/data/ibus.schemas.in
++++ b/data/ibus.schemas.in
+@@ -353,6 +353,18 @@
+ 	    <long>Custom font name for language panel</long>
+       </locale>
+     </schema>
++    <schema>
++      <key>/schemas/desktop/ibus/panel/emoji/unicode-hotkey</key>
++      <applyto>/desktop/ibus/panel/emoji/unicode-hotkey</applyto>
++      <owner>ibus</owner>
++      <type>list</type>
++      <list_type>string</list_type>
++      <default>[&lt;Control&gt;&lt;Shift&gt;u]</default>
++      <locale name="C">
++        <short>Unicode shortcut keys for gtk_accelerator_parse</short>
++          <long>The shortcut keys for turning Unicode typing on or off</long>
++      </locale>
++    </schema>
+     <schema>
+       <key>/schemas/desktop/ibus/panel/emoji/hotkey</key>
+       <applyto>/desktop/ibus/panel/emoji/hotkey</applyto>
+diff --git a/setup/main.py b/setup/main.py
+index f0eee996..f6adb098 100644
+--- a/setup/main.py
++++ b/setup/main.py
+@@ -4,7 +4,7 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2010-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2010-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ # Copyright (c) 2007-2016 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+@@ -123,10 +123,15 @@ class Setup(object):
+         name = 'emoji'
+         label = 'emoji_dialog'
+         self.__init_hotkey(name, label)
++        name = 'unicode'
++        label = 'unicode_dialog'
++        self.__init_hotkey(name, label)
+ 
+     def __init_hotkey(self, name, label, comment=None):
+         if name == 'emoji':
+             shortcuts = self.__settings_emoji.get_strv('hotkey')
++        elif name == 'unicode':
++            shortcuts = self.__settings_emoji.get_strv('unicode-hotkey')
+         else:
+             shortcuts = self.__settings_hotkey.get_strv(name)
+         button = self.__builder.get_object("button_%s" % label)
+@@ -139,6 +144,9 @@ class Setup(object):
+         if name == 'emoji':
+             button.connect("clicked", self.__shortcut_button_clicked_cb,
+                     'hotkey', 'panel/' + name, label, entry)
++        elif name == 'unicode':
++            button.connect("clicked", self.__shortcut_button_clicked_cb,
++                    'unicode-hotkey', 'panel/emoji', label, entry)
+         else:
+             button.connect("clicked", self.__shortcut_button_clicked_cb,
+                     name, "general/hotkey", label, entry)
+diff --git a/setup/setup.ui b/setup/setup.ui
+index e64b1046..f1beb1de 100644
+--- a/setup/setup.ui
++++ b/setup/setup.ui
+@@ -870,9 +870,9 @@
+                           <object class="GtkLabel" id="label_emoji1">
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+-                            <property name="tooltip_text" translatable="yes">The shortcut keys for showing emoji dialog</property>
++                            <property name="tooltip_text" translatable="yes">The shortcut keys to enable conversions of emoji annotations or Unicode names</property>
+                             <property name="halign">start</property>
+-                            <property name="label" translatable="yes">Emoji choice:</property>
++                            <property name="label" translatable="yes">Emoji annotation:</property>
+                           </object>
+                           <packing>
+                             <property name="left_attach">0</property>
+@@ -920,6 +920,60 @@
+                             <property name="top_attach">0</property>
+                           </packing>
+                         </child>
++                        <child>
++                          <object class="GtkLabel" id="label_unicode1">
++                            <property name="visible">True</property>
++                            <property name="can_focus">False</property>
++                            <property name="tooltip_text" translatable="yes">The shortcut keys to enable Unicode code point conversions</property>
++                            <property name="halign">start</property>
++                            <property name="label" translatable="yes">Unicode code point:</property>
++                          </object>
++                          <packing>
++                            <property name="left_attach">0</property>
++                            <property name="top_attach">1</property>
++                          </packing>
++                        </child>
++                        <child>
++                          <object class="GtkBox" id="hbox_unicode1">
++                            <property name="orientation">horizontal</property>
++                            <property name="visible">True</property>
++                            <property name="can_focus">False</property>
++                            <property name="spacing">6</property>
++                            <property name="hexpand">true</property>
++                            <child>
++                              <object class="GtkEntry" id="entry_unicode_dialog">
++                                <property name="visible">True</property>
++                                <property name="can_focus">True</property>
++                                <property name="editable">False</property>
++                              </object>
++                              <packing>
++                                <property name="expand">True</property>
++                                <property name="fill">True</property>
++                                <property name="position">0</property>
++                              </packing>
++                            </child>
++                            <child>
++                              <object class="GtkButton" id="button_unicode_dialog">
++                                <property name="label" translatable="yes">...</property>
++                                <property name="use_action_appearance">False</property>
++                                <property name="visible">True</property>
++                                <property name="can_focus">True</property>
++                                <property name="receives_default">False</property>
++                                <property name="use_action_appearance">False</property>
++                                <property name="use_underline">True</property>
++                              </object>
++                              <packing>
++                                <property name="expand">False</property>
++                                <property name="fill">True</property>
++                                <property name="position">1</property>
++                              </packing>
++                            </child>
++                          </object>
++                          <packing>
++                            <property name="left_attach">1</property>
++                            <property name="top_attach">1</property>
++                          </packing>
++                        </child>
+                       </object>
+                     </child>
+                     <child type="label">
+diff --git a/src/ibusengine.c b/src/ibusengine.c
+index fd61102a..a3ccd7dd 100644
+--- a/src/ibusengine.c
++++ b/src/ibusengine.c
+@@ -64,8 +64,6 @@ enum {
+ };
+ 
+ 
+-typedef struct _IBusEngineKeybinding IBusEngineKeybinding;
+-
+ /* IBusEnginePriv */
+ struct _IBusEnginePrivate {
+     gchar *engine_name;
+@@ -81,14 +79,11 @@ struct _IBusEnginePrivate {
+     guint content_purpose;
+     guint content_hints;
+ 
+-    GSettings             *settings_emoji;
+-    IBusEngineKeybinding **emoji_keybindings;
++    GHashTable            *extension_keybindings;
++    gboolean               enable_extension;
++    gchar                 *current_extension_name;
+ };
+ 
+-struct _IBusEngineKeybinding {
+-    guint            keyval;
+-    IBusModifierType modifiers;
+-};
+ 
+ static guint            engine_signals[LAST_SIGNAL] = { 0 };
+ 
+@@ -191,10 +186,6 @@ static void      ibus_engine_dbus_property_changed
+                                               const gchar        *property_name,
+                                               GVariant           *value);
+ static void      ibus_engine_keybinding_free (IBusEngine         *engine);
+-static void      settings_emoji_hotkey_changed_cb 
+-                                             (GSettings          *settings,
+-                                              const gchar        *key,
+-                                              gpointer            data);
+ 
+ 
+ G_DEFINE_TYPE (IBusEngine, ibus_engine, IBUS_TYPE_SERVICE)
+@@ -253,6 +244,12 @@ static const gchar introspection_xml[] =
+     "      <arg direction='in'  type='u' name='cursor_pos' />"
+     "      <arg direction='in'  type='u' name='anchor_pos' />"
+     "    </method>"
++    "    <method name='PanelExtensionReceived'>"
++    "      <arg direction='in'  type='v' name='event' />"
++    "    </method>"
++    "    <method name='PanelExtensionRegisterKeys'>"
++    "      <arg direction='in'  type='v' name='data' />"
++    "    </method>"
+     /* FIXME signals */
+     "    <signal name='CommitText'>"
+     "      <arg type='v' name='text' />"
+@@ -309,16 +306,22 @@ ibus_engine_class_init (IBusEngineClass *class)
+     GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+     IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (class);
+ 
+-    gobject_class->set_property = (GObjectSetPropertyFunc) ibus_engine_set_property;
+-    gobject_class->get_property = (GObjectGetPropertyFunc) ibus_engine_get_property;
++    gobject_class->set_property =
++            (GObjectSetPropertyFunc) ibus_engine_set_property;
++    gobject_class->get_property =
++            (GObjectGetPropertyFunc) ibus_engine_get_property;
+ 
+     ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_engine_destroy;
+ 
+-    IBUS_SERVICE_CLASS (class)->service_method_call  = ibus_engine_service_method_call;
+-    IBUS_SERVICE_CLASS (class)->service_get_property = ibus_engine_service_get_property;
+-    IBUS_SERVICE_CLASS (class)->service_set_property = ibus_engine_service_set_property;
++    IBUS_SERVICE_CLASS (class)->service_method_call  =
++            ibus_engine_service_method_call;
++    IBUS_SERVICE_CLASS (class)->service_get_property =
++            ibus_engine_service_get_property;
++    IBUS_SERVICE_CLASS (class)->service_set_property =
++            ibus_engine_service_set_property;
+ 
+-    ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), introspection_xml);
++    ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class),
++                                       introspection_xml);
+ 
+     class->process_key_event = ibus_engine_process_key_event;
+     class->focus_in     = ibus_engine_focus_in;
+@@ -839,26 +842,25 @@ ibus_engine_init (IBusEngine *engine)
+ {
+     IBusEnginePrivate *priv;
+     engine->priv = priv = IBUS_ENGINE_GET_PRIVATE (engine);
+-
+     priv->surrounding_text = g_object_ref_sink (text_empty);
+-    priv->settings_emoji =
+-            g_settings_new ("org.freedesktop.ibus.panel.emoji");
+-    settings_emoji_hotkey_changed_cb (priv->settings_emoji, "hotkey", engine);
+-    g_signal_connect (priv->settings_emoji, "changed::hotkey",
+-                      G_CALLBACK (settings_emoji_hotkey_changed_cb), engine);
++    priv->extension_keybindings = g_hash_table_new_full (
++            g_str_hash,
++            g_str_equal,
++            g_free,
++            g_free);
+ }
+ 
+ static void
+ ibus_engine_destroy (IBusEngine *engine)
+ {
+-    g_free (engine->priv->engine_name);
+-    engine->priv->engine_name = NULL;
++    IBusEnginePrivate *priv = engine->priv;
+ 
+-    if (engine->priv->surrounding_text) {
+-        g_object_unref (engine->priv->surrounding_text);
+-        engine->priv->surrounding_text = NULL;
+-    }
+-    ibus_engine_keybinding_free (engine);
++    g_clear_pointer (&priv->engine_name, g_free);
++    g_clear_pointer (&priv->current_extension_name, g_free);
++    if (priv->surrounding_text)
++        g_clear_object (&priv->surrounding_text);
++    if (priv->extension_keybindings)
++        g_clear_pointer (&priv->extension_keybindings, g_hash_table_destroy);
+ 
+     IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine));
+ }
+@@ -895,19 +897,38 @@ ibus_engine_get_property (IBusEngine *engine,
+ }
+ 
+ static void
+-ibus_engine_panel_extension (IBusEngine *engine)
++ibus_engine_panel_extension (IBusEngine  *engine,
++                             const gchar *name)
+ {
+-    IBusXEvent *xevent = ibus_x_event_new (
+-            "event-type", IBUS_X_EVENT_KEY_PRESS,
+-            "purpose", "emoji",
++    IBusEnginePrivate *priv;
++    IBusExtensionEvent *event;
++    GVariant *data;
++
++    g_assert (IBUS_IS_ENGINE (engine));
++    g_assert (name);
++
++    priv = engine->priv;
++    if (!g_strcmp0 (name, priv->current_extension_name))
++        priv->enable_extension = !priv->enable_extension;
++    else
++        priv->enable_extension = TRUE;
++    if (priv->enable_extension) {
++        g_free (priv->current_extension_name);
++        priv->current_extension_name = g_strdup (name);
++    }
++    event = ibus_extension_event_new (
++            "name", name,
++            "is-enabled", priv->enable_extension,
+             NULL);
+-    GVariant *data = ibus_serializable_serialize_object (
+-            IBUS_SERIALIZABLE (xevent));
++    g_assert (IBUS_IS_EXTENSION_EVENT (event));
++    data = ibus_serializable_serialize_object (
++            IBUS_SERIALIZABLE (event));
+ 
+     g_assert (data != NULL);
+     ibus_engine_emit_signal (engine,
+                              "PanelExtension",
+                              g_variant_new ("(v)", data));
++    g_object_unref (event);
+ }
+ 
+ static gboolean
+@@ -917,7 +938,8 @@ ibus_engine_filter_key_event (IBusEngine *engine,
+                               guint       state)
+ {
+     IBusEnginePrivate *priv;
+-    int i;
++    GList *names, *n;
++    IBusProcessKeyEventData *keys;
+     guint modifiers;
+ 
+     if ((state & IBUS_RELEASE_MASK) != 0)
+@@ -925,22 +947,29 @@ ibus_engine_filter_key_event (IBusEngine *engine,
+     g_return_val_if_fail (IBUS_IS_ENGINE (engine), FALSE);
+ 
+     priv = engine->priv;
+-    if (!priv->emoji_keybindings)
+-        return FALSE;
+-
+     modifiers = state & IBUS_MODIFIER_FILTER;
+     if (keyval >= IBUS_KEY_A && keyval <= IBUS_KEY_Z &&
+         (modifiers & IBUS_SHIFT_MASK) != 0) {
+         keyval = keyval - IBUS_KEY_A + IBUS_KEY_a;
+     }
+-    for (i = 0; priv->emoji_keybindings[i]; i++) {
+-        IBusEngineKeybinding *binding = priv->emoji_keybindings[i];
+-        if (binding->keyval == keyval &&
+-            binding->modifiers == modifiers) {
+-            ibus_engine_panel_extension (engine);
+-            return TRUE;
++    names = g_hash_table_get_keys (priv->extension_keybindings);
++    if (!names)
++        return FALSE;
++    for (n = names; n; n = n->next) {
++        const gchar *name = (const gchar *)n->data;
++        keys = g_hash_table_lookup (priv->extension_keybindings, name);
++        for (; keys; keys++) {
++            if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0)
++                break;
++            if (keys->keyval == keyval &&
++                keys->state == modifiers &&
++                (keys->keycode == 0 || keys->keycode == keycode)) {
++                ibus_engine_panel_extension (engine, name);
++                return TRUE;
++            }
+         }
+     }
++    g_list_free (names);
+     return FALSE;
+ }
+ 
+@@ -953,6 +982,97 @@ ibus_engine_service_authorized_method (IBusService     *service,
+     return FALSE;
+ }
+ 
++static void
++ibus_engine_service_panel_extension_register_keys (IBusEngine      *engine,
++                                                   GVariant        *parameters,
++                                                   GDBusMethodInvocation
++                                                                   *invocation)
++{
++    IBusEnginePrivate *priv = engine->priv;
++    GVariant *v1 = NULL;
++    GVariant *v2 = NULL;
++    GVariant *v3 = NULL;
++    GVariant *vkeys = NULL;
++    GVariantIter *iter1 = NULL;
++    GVariantIter *iter2 = NULL;
++    const gchar *name = NULL;
++    guint failure_id = 0;
++
++    g_variant_get (parameters, "(v)", &v1);
++    if (v1)
++        g_variant_get (v1, "(v)", &v2);
++    else
++        failure_id = 1;
++    if (v2)
++        g_variant_get (v2, "a{sv}", &iter1);
++    else
++        failure_id = 2;
++    if (iter1) {
++        while (g_variant_iter_loop (iter1, "{&sv}", &name, &vkeys)) {
++            if (vkeys)
++                g_variant_get (vkeys, "av", &iter2);
++            if (name && iter2) {
++                IBusProcessKeyEventData *keys = NULL;
++                gint num = 0;
++                while (g_variant_iter_loop (iter2, "v", &v3)) {
++                    if (v3) {
++                        guint keyval = 0;
++                        guint keycode = 0;
++                        guint state = 0;
++                        g_variant_get (v3, "(iii)",
++                                       &keyval, &keycode, &state);
++                        if (!keys)
++                            keys = g_new0 (IBusProcessKeyEventData, 2);
++                        else
++                            keys = g_renew (IBusProcessKeyEventData,
++                                            keys,
++                                            num + 2);
++                        keys[num].keyval = keyval;
++                        keys[num].keycode = keycode;
++                        keys[num].state = state;
++                        keys[num + 1].keyval = 0;
++                        keys[num + 1].keycode = 0;
++                        keys[num + 1].state = 0;
++                        g_clear_pointer (&v3, g_variant_unref);
++                        num++;
++                    } else {
++                        failure_id = 5;
++                    }
++                }
++                if (num > 0) {
++                    g_hash_table_replace (priv->extension_keybindings,
++                                          g_strdup (name),
++                                          keys);
++                } else {
++                    g_hash_table_remove (priv->extension_keybindings, name);
++                }
++                g_clear_pointer (&iter2, g_variant_iter_free);
++            } else {
++                failure_id = 4;
++            }
++            g_clear_pointer (&vkeys, g_variant_unref);
++            name = NULL;
++        }
++        g_variant_iter_free (iter1);
++    } else {
++        failure_id = 3;
++    }
++    if (failure_id == 0) {
++        g_dbus_method_invocation_return_value (invocation, NULL);
++    } else {
++        g_dbus_method_invocation_return_error (
++                invocation,
++                G_DBUS_ERROR,
++                G_DBUS_ERROR_FAILED,
++                "PanelExtensionRegisterKeys method gives NULL: %d",
++                failure_id);
++    }
++    if (v2)
++        g_variant_unref (v2);
++    if (v1)
++        g_variant_unref (v1);
++}
++
+ static void
+ ibus_engine_service_method_call (IBusService           *service,
+                                  GDBusConnection       *connection,
+@@ -964,6 +1084,7 @@ ibus_engine_service_method_call (IBusService           *service,
+                                  GDBusMethodInvocation *invocation)
+ {
+     IBusEngine *engine = IBUS_ENGINE (service);
++    IBusEnginePrivate *priv = engine->priv;
+ 
+     if (g_strcmp0 (interface_name, IBUS_INTERFACE_ENGINE) != 0) {
+         IBUS_SERVICE_CLASS (ibus_engine_parent_class)->
+@@ -1002,6 +1123,33 @@ ibus_engine_service_method_call (IBusService           *service,
+         g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", retval));
+         return;
+     }
++    if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) {
++        GVariant *arg0 = NULL;
++        IBusExtensionEvent *event = NULL;
++
++        g_variant_get (parameters, "(v)", &arg0);
++        if (arg0) {
++            event = (IBusExtensionEvent *)ibus_serializable_deserialize_object (
++                    arg0);
++        }
++        if (!event) {
++            g_dbus_method_invocation_return_error (
++                    invocation,
++                    G_DBUS_ERROR,
++                    G_DBUS_ERROR_FAILED,
++                    "PanelExtensionReceived method gives NULL");
++            return;
++        }
++        priv->enable_extension = ibus_extension_event_is_enabled (event);
++        g_dbus_method_invocation_return_value (invocation, NULL);
++        return;
++    }
++    if (g_strcmp0 (method_name, "PanelExtensionRegisterKeys") == 0) {
++        ibus_engine_service_panel_extension_register_keys (engine,
++                                                           parameters,
++                                                           invocation);
++        return;
++    }
+ 
+     static const struct {
+         gchar *member;
+@@ -1441,73 +1589,10 @@ static void
+ ibus_engine_keybinding_free (IBusEngine *engine)
+ {
+     IBusEnginePrivate *priv;
+-    int i;
+ 
+     g_return_if_fail (IBUS_IS_ENGINE (engine));
+ 
+     priv = engine->priv;
+-    if (priv->emoji_keybindings) {
+-        for (i = 0; priv->emoji_keybindings[i]; i++)
+-            g_slice_free (IBusEngineKeybinding, priv->emoji_keybindings[i]);
+-        g_clear_pointer (&priv->emoji_keybindings, g_free);
+-    }
+-}
+-
+-static IBusEngineKeybinding *
+-ibus_engine_keybinding_new (IBusEngine  *engine,
+-                            const gchar *accelerator)
+-{
+-    guint keyval = 0U;
+-    IBusModifierType modifiers = 0;
+-    IBusEngineKeybinding *binding = NULL;
+-
+-    ibus_accelerator_parse (accelerator, &keyval, &modifiers);
+-    if (keyval == 0U && modifiers == 0) {
+-        g_warning ("Failed to parse shortcut key '%s'", accelerator);
+-        return NULL;
+-    }
+-    if (modifiers & IBUS_SUPER_MASK) {
+-        modifiers^=IBUS_SUPER_MASK;
+-        modifiers|=IBUS_MOD4_MASK;
+-    }
+-
+-    binding = g_slice_new0 (IBusEngineKeybinding);
+-    binding->keyval = keyval;
+-    binding->modifiers = modifiers;
+-    return binding;
+-}
+-
+-static void
+-settings_emoji_hotkey_changed_cb (GSettings   *settings,
+-                                  const gchar *key,
+-                                  gpointer     data)
+-{
+-    IBusEngine *engine;
+-    IBusEnginePrivate *priv;
+-    gchar **accelerators;
+-    int i, j, length;
+-    g_return_if_fail (IBUS_IS_ENGINE (data));
+-    engine = IBUS_ENGINE (data);
+-    priv = engine->priv;
+-
+-    if (g_strcmp0 (key, "hotkey") != 0)
+-        return;
+-    accelerators = g_settings_get_strv (settings, key);
+-    length = g_strv_length (accelerators);
+-    ibus_engine_keybinding_free (engine);
+-    if (length == 0) {
+-        g_strfreev (accelerators);
+-        return;
+-    }
+-    priv->emoji_keybindings = g_new0 (IBusEngineKeybinding*, length + 1);
+-    for (i = 0, j = 0; i < length; i++) {
+-        IBusEngineKeybinding *binding =
+-                ibus_engine_keybinding_new (engine, accelerators[i]);
+-        if (!binding)
+-            continue;
+-        priv->emoji_keybindings[j++] = binding;
+-    }
+-    g_strfreev (accelerators);
+ }
+ 
+ IBusEngine *
+diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c
+index f37b91c3..71028ebf 100644
+--- a/src/ibuspanelservice.c
++++ b/src/ibuspanelservice.c
+@@ -57,6 +57,9 @@ enum {
+     DESTROY_CONTEXT,
+     SET_CONTENT_TYPE,
+     PANEL_EXTENSION_RECEIVED,
++    PROCESS_KEY_EVENT,
++    COMMIT_TEXT_RECEIVED,
++    CANDIDATE_CLICKED_LOOKUP_TABLE,
+     LAST_SIGNAL,
+ };
+ 
+@@ -153,7 +156,7 @@ static void      ibus_panel_service_set_content_type
+                                     guint                   hints);
+ static void      ibus_panel_service_panel_extension_received
+                                    (IBusPanelService       *panel,
+-                                    GVariant               *data);
++                                    IBusExtensionEvent     *event);
+ 
+ G_DEFINE_TYPE (IBusPanelService, ibus_panel_service, IBUS_TYPE_SERVICE)
+ 
+@@ -184,6 +187,11 @@ static const gchar introspection_xml[] =
+     "    <method name='CursorDownLookupTable' />"
+     "    <method name='PageUpLookupTable' />"
+     "    <method name='PageDownLookupTable' />"
++    "    <method name='CandidateClickedLookupTable'>"
++    "      <arg direction='in' type='u' name='index' />"
++    "      <arg direction='in' type='u' name='button' />"
++    "      <arg direction='in' type='u' name='state' />"
++    "    </method>"
+     "    <method name='RegisterProperties'>"
+     "      <arg direction='in'  type='v' name='props' />"
+     "    </method>"
+@@ -221,7 +229,16 @@ static const gchar introspection_xml[] =
+     "      <arg direction='in'  type='u' name='hints' />"
+     "    </method>"
+     "    <method name='PanelExtensionReceived'>"
+-    "      <arg direction='in' type='v' name='data' />"
++    "      <arg direction='in' type='v' name='event' />"
++    "    </method>"
++    "    <method name='ProcessKeyEvent'>"
++    "      <arg direction='in'  type='u' name='keyval' />"
++    "      <arg direction='in'  type='u' name='keycode' />"
++    "      <arg direction='in'  type='u' name='state' />"
++    "      <arg direction='out' type='b' />"
++    "    </method>"
++    "    <method name='CommitTextReceived'>"
++    "      <arg direction='in' type='v' name='text' />"
+     "    </method>"
+     /* Signals */
+     "    <signal name='CursorUp' />"
+@@ -247,7 +264,23 @@ static const gchar introspection_xml[] =
+     "      <arg type='v' name='text' />"
+     "    </signal>"
+     "    <signal name='PanelExtension'>"
++    "      <arg type='v' name='event' />"
++    "    </signal>"
++    "    <method name='PanelExtensionRegisterKeys'>"
+     "      <arg type='v' name='data' />"
++    "    </method>"
++    "    <signal name='UpdatePreeditTextReceived'>"
++    "      <arg type='v' name='text' />"
++    "      <arg type='u' name='cursor_pos' />"
++    "      <arg type='b' name='visible' />"
++    "    </signal>"
++    "    <signal name='UpdateAuxiliaryTextReceived'>"
++    "      <arg type='v' name='text' />"
++    "      <arg type='b' name='visible' />"
++    "    </signal>"
++    "    <signal name='UpdateLookupTableReceived'>"
++    "      <arg type='v' name='table' />"
++    "      <arg type='b' name='visible' />"
+     "    </signal>"
+     "  </interface>"
+     "</node>";
+@@ -927,10 +960,81 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
+             G_SIGNAL_RUN_LAST,
+             G_STRUCT_OFFSET (IBusPanelServiceClass, panel_extension_received),
+             NULL, NULL,
+-            _ibus_marshal_VOID__VARIANT,
++            _ibus_marshal_VOID__OBJECT,
++            G_TYPE_NONE,
++            1,
++            IBUS_TYPE_EXTENSION_EVENT);
++
++    /**
++     * IBusPanelService::process-key-event:
++     * @panel: An #IBusPanelService
++     * @keyval: Key symbol of the key press.
++     * @keycode: KeyCode of the key press.
++     * @state: Key modifier flags.
++     *
++     * Emitted when a key event is received.
++     * Implement the member function IBusPanelServiceClass::process_key_event
++     * in extended class to receive this signal.
++     * Both the key symbol and keycode are passed to the member function.
++     * See ibus_input_context_process_key_event() for further explanation of
++     * key symbol, keycode and which to use.
++     *
++     * Returns: %TRUE for successfully process the key; %FALSE otherwise.
++     * See also:  ibus_input_context_process_key_event().
++     *
++     * <note><para>Argument @user_data is ignored in this function.</para>
++     * </note>
++     */
++    panel_signals[PROCESS_KEY_EVENT] =
++        g_signal_new (I_("process-key-event"),
++            G_TYPE_FROM_CLASS (gobject_class),
++            G_SIGNAL_RUN_LAST,
++            G_STRUCT_OFFSET (IBusPanelServiceClass, process_key_event),
++            g_signal_accumulator_true_handled, NULL,
++            _ibus_marshal_BOOL__UINT_UINT_UINT,
++            G_TYPE_BOOLEAN,
++            3,
++            G_TYPE_UINT,
++            G_TYPE_UINT,
++            G_TYPE_UINT);
++
++    /**
++     * IBusPanelService::commit-text-received:
++     * @panel: An #IBusPanelService
++     * @text: A #IBusText
++     *
++     * Emitted when the client application get the ::commit-text-received.
++     * Implement the member function
++     * IBusPanelServiceClass::commit_text_received in extended class to
++     * receive this signal.
++     *
++     * <note><para>Argument @user_data is ignored in this function.</para>
++     * </note>
++     */
++    panel_signals[COMMIT_TEXT_RECEIVED] =
++        g_signal_new (I_("commit-text-received"),
++            G_TYPE_FROM_CLASS (gobject_class),
++            G_SIGNAL_RUN_LAST,
++            G_STRUCT_OFFSET (IBusPanelServiceClass, commit_text_received),
++            NULL, NULL,
++            _ibus_marshal_VOID__OBJECT,
+             G_TYPE_NONE,
+             1,
+-            G_TYPE_VARIANT);
++            IBUS_TYPE_TEXT);
++
++    panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE] =
++        g_signal_new (I_("candidate-clicked-lookup-table"),
++            G_TYPE_FROM_CLASS (gobject_class),
++            G_SIGNAL_RUN_LAST,
++            G_STRUCT_OFFSET (IBusPanelServiceClass,
++                             candidate_clicked_lookup_table),
++            NULL, NULL,
++            _ibus_marshal_VOID__UINT_UINT_UINT,
++            G_TYPE_NONE,
++            3,
++            G_TYPE_UINT,
++            G_TYPE_UINT,
++            G_TYPE_UINT);
+ }
+ 
+ static void
+@@ -1129,9 +1233,14 @@ ibus_panel_service_service_method_call (IBusService           *service,
+     }
+ 
+     if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) {
+-        GVariant *variant = NULL;
+-        g_variant_get (parameters, "(v)", &variant);
+-        if (variant == NULL) {
++        GVariant *arg0 = NULL;
++        IBusExtensionEvent *event = NULL;
++        g_variant_get (parameters, "(v)", &arg0);
++        if (arg0) {
++            event = IBUS_EXTENSION_EVENT (ibus_serializable_deserialize (arg0));
++            g_variant_unref (arg0);
++        }
++        if (!event) {
+             g_dbus_method_invocation_return_error (
+                     invocation,
+                     G_DBUS_ERROR,
+@@ -1140,11 +1249,63 @@ ibus_panel_service_service_method_call (IBusService           *service,
+             return;
+         }
+         g_signal_emit (panel, panel_signals[PANEL_EXTENSION_RECEIVED], 0,
+-                       variant);
+-        g_variant_unref (variant);
++                       event);
++        _g_object_unref_if_floating (event);
+         g_dbus_method_invocation_return_value (invocation, NULL);
+         return;
+     }
++    if (g_strcmp0 (method_name, "ProcessKeyEvent") == 0) {
++        guint keyval, keycode, state;
++        gboolean retval = FALSE;
++
++        g_variant_get (parameters, "(uuu)", &keyval, &keycode, &state);
++        g_signal_emit (panel,
++                       panel_signals[PROCESS_KEY_EVENT],
++                       0,
++                       keyval,
++                       keycode,
++                       state,
++                       &retval);
++        g_dbus_method_invocation_return_value (invocation,
++                                               g_variant_new ("(b)", retval));
++        return;
++    }
++    if (g_strcmp0 (method_name, "CommitTextReceived") == 0) {
++        GVariant *arg0 = NULL;
++        IBusText *text = NULL;
++
++        g_variant_get (parameters, "(v)", &arg0);
++        if (arg0) {
++            text = (IBusText *) ibus_serializable_deserialize (arg0);
++            g_variant_unref (arg0);
++        }
++        if (!text) {
++            g_dbus_method_invocation_return_error (
++                    invocation,
++                    G_DBUS_ERROR,
++                    G_DBUS_ERROR_FAILED,
++                    "CommitTextReceived method gives NULL");
++            return;
++        }
++        g_signal_emit (panel,
++                       panel_signals[COMMIT_TEXT_RECEIVED],
++                       0,
++                       text);
++        _g_object_unref_if_floating (text);
++        return;
++    }
++    if (g_strcmp0 (method_name, "CandidateClickedLookupTable") == 0) {
++        guint index = 0;
++        guint button = 0;
++        guint state = 0;
++        g_variant_get (parameters, "(uuu)", &index, &button, &state);
++        g_signal_emit (panel,
++                       panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE],
++                       0,
++                       index, button, state);
++        return;
++    }
++
+ 
+     const static struct {
+         const gchar *name;
+@@ -1318,8 +1479,8 @@ ibus_panel_service_set_content_type (IBusPanelService *panel,
+ }
+ 
+ static void
+-ibus_panel_service_panel_extension_received (IBusPanelService *panel,
+-                                             GVariant         *data)
++ibus_panel_service_panel_extension_received (IBusPanelService   *panel,
++                                             IBusExtensionEvent *event)
+ {
+     ibus_panel_service_not_implemented(panel);
+ }
+@@ -1396,10 +1557,11 @@ void
+ ibus_panel_service_commit_text (IBusPanelService *panel,
+                                 IBusText         *text)
+ {
++    GVariant *variant;
+     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
+     g_return_if_fail (IBUS_IS_TEXT (text));
+ 
+-    GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text);
++    variant = ibus_serializable_serialize ((IBusSerializable *)text);
+     ibus_service_emit_signal ((IBusService *) panel,
+                               NULL,
+                               IBUS_INTERFACE_PANEL,
+@@ -1413,18 +1575,144 @@ ibus_panel_service_commit_text (IBusPanelService *panel,
+ }
+ 
+ void
+-ibus_panel_service_panel_extension (IBusPanelService *panel,
+-                                    GVariant         *variant)
++ibus_panel_service_panel_extension (IBusPanelService   *panel,
++                                    IBusExtensionEvent *event)
+ {
++    GVariant *variant;
+     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
+-    g_return_if_fail (variant);
++    g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event));
+ 
++    variant = ibus_serializable_serialize ((IBusSerializable *)event);
+     ibus_service_emit_signal ((IBusService *) panel,
+                               NULL,
+                               IBUS_INTERFACE_PANEL,
+                               "PanelExtension",
+                               g_variant_new ("(v)", variant),
+                               NULL);
++
++    if (g_object_is_floating (event)) {
++        g_object_unref (event);
++    }
++}
++
++void
++ibus_panel_service_panel_extension_register_keys (IBusPanelService   *panel,
++                                                  const gchar
++                                                           *first_property_name,
++                                                  ...)
++{
++    GVariantBuilder builder;
++    GVariantBuilder child;
++    const gchar *name;
++    va_list var_args;
++    IBusProcessKeyEventData *keys;
++
++    g_return_if_fail (first_property_name);
++
++    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
++    name = first_property_name;
++
++    va_start (var_args, first_property_name);
++    do {
++        keys = va_arg (var_args, IBusProcessKeyEventData *);
++        g_return_if_fail (keys != NULL);
++        g_variant_builder_init (&child, G_VARIANT_TYPE ("av"));
++        for (; keys; keys++) {
++            if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0)
++                break;
++            g_variant_builder_add (&child, "v",
++                                   g_variant_new ("(iii)",
++                                                  keys->keyval,
++                                                  keys->keycode, 
++                                                  keys->state));
++        }
++        g_variant_builder_add (&builder, "{sv}",
++                               g_strdup (name), g_variant_builder_end (&child));
++    } while ((name = va_arg (var_args, const gchar *)));
++    va_end (var_args);
++
++    ibus_service_emit_signal ((IBusService *) panel,
++                              NULL,
++                              IBUS_INTERFACE_PANEL,
++                              "PanelExtensionRegisterKeys",
++                              g_variant_new ("(v)",
++                                             g_variant_builder_end (&builder)),
++                              NULL);
++}
++
++void
++ibus_panel_service_update_preedit_text_received (IBusPanelService *panel,
++                                                 IBusText         *text,
++                                                 guint             cursor_pos,
++                                                 gboolean          visible)
++{
++    GVariant *variant;
++
++    g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
++    g_return_if_fail (IBUS_IS_TEXT (text));
++
++    variant = ibus_serializable_serialize ((IBusSerializable *)text);
++    g_return_if_fail (variant);
++    ibus_service_emit_signal ((IBusService *) panel,
++                              NULL,
++                              IBUS_INTERFACE_PANEL,
++                              "UpdatePreeditTextReceived",
++                              g_variant_new ("(vub)",
++                                             variant, cursor_pos, visible),
++                              NULL);
++
++    if (g_object_is_floating (text)) {
++        g_object_unref (text);
++    }
++}
++
++void
++ibus_panel_service_update_auxiliary_text_received (IBusPanelService *panel,
++                                                   IBusText         *text,
++                                                   gboolean          visible)
++{
++    GVariant *variant;
++    g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
++    g_return_if_fail (IBUS_IS_TEXT (text));
++
++    variant = ibus_serializable_serialize ((IBusSerializable *)text);
++    g_return_if_fail (variant);
++    ibus_service_emit_signal ((IBusService *) panel,
++                              NULL,
++                              IBUS_INTERFACE_PANEL,
++                              "UpdateAuxiliaryTextReceived",
++                              g_variant_new ("(vb)",
++                                             variant, visible),
++                              NULL);
++
++    if (g_object_is_floating (text)) {
++        g_object_unref (text);
++    }
++}
++
++void
++ibus_panel_service_update_lookup_table_received (IBusPanelService *panel,
++                                                 IBusLookupTable  *table,
++                                                 gboolean          visible)
++{
++    GVariant *variant;
++
++    g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
++    g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table));
++
++    variant = ibus_serializable_serialize ((IBusSerializable *)table);
++    g_return_if_fail (variant);
++    ibus_service_emit_signal ((IBusService *) panel,
++                              NULL,
++                              IBUS_INTERFACE_PANEL,
++                              "UpdateLookupTableReceived",
++                              g_variant_new ("(vb)",
++                                             variant, visible),
++                              NULL);
++
++    if (g_object_is_floating (table)) {
++        g_object_unref (table);
++    }
+ }
+ 
+ #define DEFINE_FUNC(name, Name)                             \
+diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h
+index 60ef842b..d91f2309 100644
+--- a/src/ibuspanelservice.h
++++ b/src/ibuspanelservice.h
+@@ -38,6 +38,7 @@
+ #include "ibuslookuptable.h"
+ #include "ibusservice.h"
+ #include "ibusproplist.h"
++#include "ibusxevent.h"
+ 
+ /*
+  * Type macros.
+@@ -130,11 +131,24 @@ struct _IBusPanelServiceClass {
+                                             gint                    h);
+     void     (* panel_extension_received)
+                                            (IBusPanelService       *panel,
+-                                            GVariant               *data);
++                                            IBusExtensionEvent     *event);
++    gboolean (* process_key_event)
++                                           (IBusPanelService       *panel,
++                                            guint                   keyval,
++                                            guint                   keycode,
++                                            guint                   state);
++    void     (* commit_text_received)
++                                           (IBusPanelService       *panel,
++                                            IBusText               *text);
++    void     (* candidate_clicked_lookup_table)
++                                           (IBusPanelService       *panel,
++                                            guint                   index,
++                                            guint                   button,
++                                            guint                   state);
+ 
+     /*< private >*/
+     /* padding */
+-    gpointer pdummy[5];  // We can add 8 pointers without breaking the ABI.
++    gpointer pdummy[2];  // We can add 8 pointers without breaking the ABI.
+ };
+ 
+ GType            ibus_panel_service_get_type  (void);
+@@ -248,12 +262,105 @@ void ibus_panel_service_commit_text       (IBusPanelService *panel,
+ /**
+  * ibus_panel_service_panel_extension:
+  * @panel: An #IBusPanelService
+- * @data: (transfer full): A #GVariant data which is sent to a panel extension. 
++ * @event: (transfer full): A #PanelExtensionEvent which is sent to a
++ *                          panel extension. 
+  *
++ * Enable or disable a panel extension with #IBusExtensionEvent.
+  * 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);
++void ibus_panel_service_panel_extension   (IBusPanelService   *panel,
++                                           IBusExtensionEvent *event);
++
++/**
++ * ibus_panel_service_panel_extension_register_keys:
++ * @panel: An #IBusPanelService
++ * @first_property_name: the first name of the shortcut keys. This is %NULL
++ " terminated.
++ *
++ * Register shortcut keys to enable panel extensions with #IBusExtensionEvent.
++ * Notify that a data is sent
++ * by sending a "PanelExtensionRegisterKeys" message to IBus panel extension
++ * service. Seems Vala does not support uint[][3] and use
++ * IBusProcessKeyEventData[]. E.g.
++ * IBusProcessKeyEventData[] keys = {{
++ *         IBUS_KEY_e, 0, IBUS_SHIFT_MASK | IBUS_SUPER_MASK }};
++ * ibus_panel_service_panel_extension_register_keys(panel, "emoji", keys, NULL);
++ */
++void ibus_panel_service_panel_extension_register_keys
++                                           (IBusPanelService  *panel,
++                                            const gchar       *first_property_name,
++                                            ...);
++
++/**
++ * ibus_panel_service_update_preedit_text_received:
++ * @panel: An #IBusPanelService
++ * @text: Update content.
++ * @cursor_pos: Current position of cursor
++ * @visible: Whether the pre-edit buffer is visible.
++ *
++ * Notify that the preedit is updated by the panel extension
++ *
++ * (Note: The table object will be released, if it is floating.
++ *  If caller want to keep the object, caller should make the object
++ *  sink by g_object_ref_sink.)
++ */
++void ibus_panel_service_update_preedit_text_received
++                                          (IBusPanelService *panel,
++                                           IBusText         *text,
++                                           guint             cursor_pos,
++                                           gboolean          visible);
++
++/**
++ * ibus_panel_service_show_preedit_text_received:
++ * @panel: An IBusPanelService
++ *
++ * Notify that the preedit is shown by the panel extension
++ */
++void ibus_panel_service_show_preedit_text_received
++                                          (IBusPanelService *panel);
++
++/**
++ * ibus_panel_service_hide_preedit_text_received:
++ * @panel: An IBusPanelService
++ *
++ * Notify that the preedit is hidden by the panel extension
++ */
++void ibus_panel_service_hide_preedit_text_received
++                                          (IBusPanelService *panel);
++
++/**
++ * ibus_panel_service_update_auxiliary_text_received:
++ * @panel: An #IBusPanelService
++ * @text: An #IBusText
++ * @visible: Whether the auxilirary text is visible.
++ *
++ * Notify that the auxilirary is updated by the panel extension.
++ *
++ * (Note: The table object will be released, if it is floating.
++ *  If caller want to keep the object, caller should make the object
++ *  sink by g_object_ref_sink.)
++ */
++void ibus_panel_service_update_auxiliary_text_received
++                                          (IBusPanelService *panel,
++                                           IBusText         *text,
++                                           gboolean          visible);
++
++/**
++ * ibus_panel_service_update_lookup_table_received:
++ * @panel: An #IBusPanelService
++ * @table: An #IBusLookupTable
++ * @visible: Whether the lookup table is visible.
++ *
++ * Notify that the lookup table is updated by the panel extension.
++ *
++ * (Note: The table object will be released, if it is floating.
++ *  If caller want to keep the object, caller should make the object
++ *  sink by g_object_ref_sink.)
++ */
++void ibus_panel_service_update_lookup_table_received
++                                          (IBusPanelService *panel,
++                                           IBusLookupTable  *table,
++                                           gboolean          visible);
+ G_END_DECLS
+ #endif
+diff --git a/src/ibusshare.h b/src/ibusshare.h
+index 757d915b..4f5a306b 100644
+--- a/src/ibusshare.h
++++ b/src/ibusshare.h
+@@ -73,6 +73,15 @@
+  */
+ #define IBUS_SERVICE_PANEL_EXTENSION "org.freedesktop.IBus.Panel.Extension"
+ 
++/**
++ * IBUS_SERVICE_PANEL_EXTENSION_EMOJI:
++ *
++ * Address of IBus panel extension service for emoji.
++ * This service provides emoji, Unicode code point, Unicode name features.
++ */
++#define IBUS_SERVICE_PANEL_EXTENSION_EMOJI \
++        "org.freedesktop.IBus.Panel.Extension.Emoji"
++
+ /**
+  * IBUS_SERVICE_CONFIG:
+  *
+@@ -109,11 +118,13 @@
+ #define IBUS_PATH_PANEL         "/org/freedesktop/IBus/Panel"
+ 
+ /**
+- * IBUS_PATH_PANEL_EXTENSION:
++ * IBUS_PATH_PANEL_EXTENSION_EMOJI:
+  *
+- * D-Bus path for IBus panel.
++ * D-Bus path for IBus extension panel for emoji.
++ * This service provides emoji, Unicode code point, Unicode name features.
+  */
+-#define IBUS_PATH_PANEL_EXTENSION "/org/freedesktop/IBus/Panel/Extension"
++#define IBUS_PATH_PANEL_EXTENSION_EMOJI \
++        "/org/freedesktop/IBus/Panel/Extension/Emoji"
+ 
+ /**
+  * IBUS_PATH_CONFIG:
+diff --git a/src/ibusxevent.c b/src/ibusxevent.c
+index dea80272..287bb99b 100644
+--- a/src/ibusxevent.c
++++ b/src/ibusxevent.c
+@@ -22,13 +22,23 @@
+ #include "ibusinternal.h"
+ #include "ibusxevent.h"
+ 
++#define IBUS_EXTENSION_EVENT_VERSION 1
++#define IBUS_EXTENSION_EVENT_GET_PRIVATE(o)                             \
++   (G_TYPE_INSTANCE_GET_PRIVATE ((o),                                   \
++                                 IBUS_TYPE_EXTENSION_EVENT,             \
++                                 IBusExtensionEventPrivate))
++
+ #define IBUS_X_EVENT_VERSION 1
+-#define IBUS_X_EVENT_GET_PRIVATE(o)  \
++#define IBUS_X_EVENT_GET_PRIVATE(o)                                     \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_X_EVENT, IBusXEventPrivate))
+ 
+ enum {
+     PROP_0,
+     PROP_VERSION,
++    PROP_NAME,
++    PROP_IS_ENABLED,
++    PROP_IS_EXTENSION,
++    PROP_PARAMS,
+     PROP_EVENT_TYPE,
+     PROP_WINDOW,
+     PROP_SEND_EVENT,
+@@ -52,6 +62,14 @@ enum {
+ };
+ 
+ 
++struct _IBusExtensionEventPrivate {
++    guint     version;
++    gchar    *name;
++    gboolean  is_enabled;
++    gboolean  is_extension;
++    gchar    *params;
++};
++
+ struct _IBusXEventPrivate {
+     guint    version;
+     guint32  time;
+@@ -73,24 +91,346 @@ struct _IBusXEventPrivate {
+ };
+ 
+ /* 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);
+-
++static void      ibus_extension_event_destroy      (IBusExtensionEvent *event);
++static void      ibus_extension_event_set_property (IBusExtensionEvent *event,
++                                                    guint               prop_id,
++                                                    const GValue       *value,
++                                                    GParamSpec         *pspec);
++static void      ibus_extension_event_get_property (IBusExtensionEvent *event,
++                                                    guint               prop_id,
++                                                    GValue             *value,
++                                                    GParamSpec         *pspec);
++static gboolean  ibus_extension_event_serialize    (IBusExtensionEvent *event,
++                                                    GVariantBuilder
++                                                                      *builder);
++static gint      ibus_extension_event_deserialize  (IBusExtensionEvent *event,
++                                                    GVariant
++                                                                      *variant);
++static gboolean  ibus_extension_event_copy         (IBusExtensionEvent
++                                                                          *dest,
++                                                    const IBusExtensionEvent
++                                                                          *src);
++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 (IBusExtensionEvent, ibus_extension_event, IBUS_TYPE_SERIALIZABLE)
+ G_DEFINE_TYPE (IBusXEvent, ibus_x_event, IBUS_TYPE_SERIALIZABLE)
+ 
++static void
++ibus_extension_event_class_init (IBusExtensionEventClass *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_extension_event_set_property;
++    gobject_class->get_property =
++            (GObjectGetPropertyFunc) ibus_extension_event_get_property;
++
++    object_class->destroy =
++            (IBusObjectDestroyFunc) ibus_extension_event_destroy;
++
++    serializable_class->serialize   =
++            (IBusSerializableSerializeFunc) ibus_extension_event_serialize;
++    serializable_class->deserialize =
++            (IBusSerializableDeserializeFunc) ibus_extension_event_deserialize;
++    serializable_class->copy        =
++            (IBusSerializableCopyFunc) ibus_extension_event_copy;
++
++    /* install properties */
++    /**
++     * IBusExtensionEvent:version:
++     *
++     * Version of the #IBusExtensionEvent.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_VERSION,
++                    g_param_spec_uint ("version",
++                        "version",
++                        "version",
++                        0,
++                        G_MAXUINT32,
++                        IBUS_EXTENSION_EVENT_VERSION,
++                        G_PARAM_READABLE));
++
++    /**
++     * IBusExtensionEvent:name:
++     *
++     * Name of the extension in the #IBusExtensionEvent.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_NAME,
++                    g_param_spec_string ("name",
++                        "name",
++                        "name of the extension",
++                        "",
++                        G_PARAM_READWRITE |
++                        G_PARAM_CONSTRUCT_ONLY));
++
++    /**
++     * IBusExtensionEvent:is-enabled:
++     *
++     * %TRUE if the extension is enabled in the #IBusExtensionEvent.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_IS_ENABLED,
++                    g_param_spec_boolean ("is-enabled",
++                        "is enabled",
++                        "if the extension is enabled",
++                        FALSE,
++                        G_PARAM_READWRITE |
++                        G_PARAM_CONSTRUCT_ONLY));
++
++    /**
++     * IBusExtensionEvent:is-extension:
++     *
++     * %TRUE if the #IBusExtensionEvent is called by an extension.
++     * %FALSE if the #IBusExtensionEvent is called by an active engine or
++     * panel.
++     * If this value is %TRUE, the event is send to ibus-daemon, an active
++     * engine. If it's %FALSE, the event is sned to ibus-daemon, panels.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_IS_EXTENSION,
++                    g_param_spec_boolean ("is-extension",
++                        "is extension",
++                        "if the event is called by an extension",
++                        FALSE,
++                        G_PARAM_READWRITE |
++                        G_PARAM_CONSTRUCT_ONLY));
++
++    /**
++     * IBusExtensionEvent:params:
++     *
++     * Parameters to enable the extension in the #IBusExtensionEvent.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_PARAMS,
++                    g_param_spec_string ("params",
++                        "params",
++                        "Parameters to enable the extension",
++                        "",
++                        G_PARAM_READWRITE |
++                        G_PARAM_CONSTRUCT_ONLY));
++
++    g_type_class_add_private (class, sizeof (IBusExtensionEventPrivate));
++}
++
++static void
++ibus_extension_event_init (IBusExtensionEvent *event)
++{
++    event->priv = IBUS_EXTENSION_EVENT_GET_PRIVATE (event);
++    event->priv->version = IBUS_EXTENSION_EVENT_VERSION;
++}
++
++static void
++ibus_extension_event_destroy (IBusExtensionEvent *event)
++{
++    g_clear_pointer (&event->priv->name, g_free);
++
++    IBUS_OBJECT_CLASS(ibus_extension_event_parent_class)->
++            destroy (IBUS_OBJECT (event));
++}
++
++static void
++ibus_extension_event_set_property (IBusExtensionEvent   *event,
++                                   guint         prop_id,
++                                   const GValue *value,
++                                   GParamSpec   *pspec)
++{
++    IBusExtensionEventPrivate *priv = event->priv;
++
++    switch (prop_id) {
++    case PROP_NAME:
++        g_free (priv->name);
++        priv->name = g_value_dup_string (value);
++        break;
++    case PROP_IS_ENABLED:
++        priv->is_enabled = g_value_get_boolean (value);
++        break;
++    case PROP_IS_EXTENSION:
++        priv->is_extension = g_value_get_boolean (value);
++        break;
++    case PROP_PARAMS:
++        priv->params = g_value_dup_string (value);
++        break;
++    default:
++        G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
++    }
++}
++
++static void
++ibus_extension_event_get_property (IBusExtensionEvent *event,
++                                   guint               prop_id,
++                                   GValue             *value,
++                                   GParamSpec         *pspec)
++{
++    IBusExtensionEventPrivate *priv = event->priv;
++    switch (prop_id) {
++    case PROP_VERSION:
++        g_value_set_uint (value, priv->version);
++        break;
++    case PROP_NAME:
++        g_value_set_string (value, priv->name);
++        break;
++    case PROP_IS_ENABLED:
++        g_value_set_boolean (value, priv->is_enabled);
++        break;
++    case PROP_IS_EXTENSION:
++        g_value_set_boolean (value, priv->is_extension);
++        break;
++    case PROP_PARAMS:
++        g_value_set_string (value, priv->params);
++        break;
++    default:
++        G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
++    }
++}
++
++static gboolean
++ibus_extension_event_serialize (IBusExtensionEvent *event,
++                                GVariantBuilder    *builder)
++{
++    gboolean retval;
++    IBusExtensionEventPrivate *priv;
++
++    retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_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, "s", NOTNULL (priv->name));
++    g_variant_builder_add (builder, "b", priv->is_enabled);
++    g_variant_builder_add (builder, "b", priv->is_extension);
++    g_variant_builder_add (builder, "s", NOTNULL (priv->params));
++#undef NOTNULL
++
++    return TRUE;
++}
++
++static gint
++ibus_extension_event_deserialize (IBusExtensionEvent *event,
++                                  GVariant           *variant)
++{
++    gint retval;
++    IBusExtensionEventPrivate *priv;
++
++    retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_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);
++    ibus_g_variant_get_child_string (variant, retval++,
++                                     &priv->name);
++    g_variant_get_child (variant, retval++, "b", &priv->is_enabled);
++    g_variant_get_child (variant, retval++, "b", &priv->is_extension);
++    ibus_g_variant_get_child_string (variant, retval++,
++                                     &priv->params);
++
++    return retval;
++}
++
++static gboolean
++ibus_extension_event_copy (IBusExtensionEvent       *dest,
++                           const IBusExtensionEvent *src)
++{
++    gboolean retval;
++    IBusExtensionEventPrivate *dest_priv = dest->priv;
++    IBusExtensionEventPrivate *src_priv = src->priv;
++
++    retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)->
++            copy ((IBusSerializable *)dest, (IBusSerializable *)src);
++    g_return_val_if_fail (retval, FALSE);
++
++    dest_priv->version           = src_priv->version;
++    dest_priv->name              = g_strdup (src_priv->name);
++    dest_priv->is_enabled        = src_priv->is_enabled;
++    dest_priv->is_extension      = src_priv->is_extension;
++    dest_priv->params            = g_strdup (src_priv->params);
++    return TRUE;
++}
++
++IBusExtensionEvent *
++ibus_extension_event_new (const gchar   *first_property_name,
++                          ...)
++{
++    va_list var_args;
++    IBusExtensionEvent *event;
++
++    va_start (var_args, first_property_name);
++    event = (IBusExtensionEvent *) g_object_new_valist (
++            IBUS_TYPE_EXTENSION_EVENT,
++            first_property_name,
++            var_args);
++    va_end (var_args);
++    g_assert (event->priv->version != 0);
++    return event;
++}
++
++guint
++ibus_extension_event_get_version (IBusExtensionEvent *event)
++{
++    g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), 0);
++    return event->priv->version;
++}
++
++const gchar *
++ibus_extension_event_get_name (IBusExtensionEvent *event)
++{
++    g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), "");
++    return event->priv->name;
++}
++
++gboolean
++ibus_extension_event_is_enabled (IBusExtensionEvent *event)
++{
++    g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE);
++    return event->priv->is_enabled;
++}
++
++gboolean
++ibus_extension_event_is_extension (IBusExtensionEvent *event)
++{
++    g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE);
++    return event->priv->is_extension;
++}
++
++const gchar *
++ibus_extension_event_get_params (IBusExtensionEvent *event)
++{
++    g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), "");
++    return event->priv->params;
++}
++
++
+ static void
+ ibus_x_event_class_init (IBusXEventClass *class)
+ {
+@@ -454,6 +794,7 @@ static void
+ ibus_x_event_destroy (IBusXEvent *event)
+ {
+     g_clear_pointer (&event->priv->string, g_free);
++    g_clear_pointer (&event->priv->purpose, g_free);
+ 
+     IBUS_OBJECT_CLASS(ibus_x_event_parent_class)->destroy (IBUS_OBJECT (event));
+ }
+diff --git a/src/ibusxevent.h b/src/ibusxevent.h
+index f35f14e4..d44cc8f4 100644
+--- a/src/ibusxevent.h
++++ b/src/ibusxevent.h
+@@ -29,8 +29,8 @@
+ 
+ /**
+  * SECTION: ibusxevent
+- * @short_description: XEvent wrapper object
+- * @title: IBusXEvent
++ * @short_description: Extension Event wrapper object
++ * @title: IBusExtensionEvent
+  * @stability: Unstable
+  *
+  * An IBusXEvent provides a wrapper of XEvent.
+@@ -45,25 +45,150 @@
+  */
+ 
+ /* define GOBJECT macros */
+-#define IBUS_TYPE_X_EVENT            \
++#define IBUS_TYPE_EXTENSION_EVENT                                       \
++    (ibus_extension_event_get_type ())
++#define IBUS_EXTENSION_EVENT(obj)                                       \
++    (G_TYPE_CHECK_INSTANCE_CAST ((obj),                                 \
++                                 IBUS_TYPE_EXTENSION_EVENT,             \
++                                 IBusExtensionEvent))
++#define IBUS_EXTENSION_EVENT_CLASS(klass)                               \
++    (G_TYPE_CHECK_CLASS_CAST ((klass),                                  \
++                              IBUS_TYPE_EXTENSION_EVENT,                \
++                              IBusExtensionEventClass))
++#define IBUS_IS_EXTENSION_EVENT(obj)                                    \
++    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_EXTENSION_EVENT))
++#define IBUS_IS_EXTENSION_EVENT_CLASS(klass)                            \
++    (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_EXTENSION_EVENT))
++#define IBUS_EXTENSION_EVENT_GET_CLASS(obj)                             \
++    (G_TYPE_INSTANCE_GET_CLASS ((obj),                                  \
++                                IBUS_TYPE_EXTENSION_EVENT,              \
++                                IBusExtensionEventClass))
++
++#define IBUS_TYPE_X_EVENT                                               \
+     (ibus_x_event_get_type ())
+-#define IBUS_X_EVENT(obj)            \
++#define IBUS_X_EVENT(obj)                                               \
+     (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_X_EVENT, IBusXEvent))
+-#define IBUS_X_EVENT_CLASS(klass)    \
++#define IBUS_X_EVENT_CLASS(klass)                                       \
+     (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_X_EVENT, IBusXEventClass))
+-#define IBUS_IS_X_EVENT(obj)         \
++#define IBUS_IS_X_EVENT(obj)                                            \
+     (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_X_EVENT))
+-#define IBUS_IS_X_EVENT_CLASS(klass) \
++#define IBUS_IS_X_EVENT_CLASS(klass)                                    \
+     (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_X_EVENT))
+-#define IBUS_X_EVENT_GET_CLASS(obj)  \
++#define IBUS_X_EVENT_GET_CLASS(obj)                                     \
+     (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_X_EVENT, IBusXEventClass))
+ 
+ G_BEGIN_DECLS
+ 
++typedef struct _IBusProcessKeyEventData IBusProcessKeyEventData;
++typedef struct _IBusExtensionEvent IBusExtensionEvent;
++typedef struct _IBusExtensionEventClass IBusExtensionEventClass;
++typedef struct _IBusExtensionEventPrivate IBusExtensionEventPrivate;
+ typedef struct _IBusXEvent IBusXEvent;
+ typedef struct _IBusXEventClass IBusXEventClass;
+ typedef struct _IBusXEventPrivate IBusXEventPrivate;
+ 
++/**
++ * IBusProcessKeyEventData:
++ *
++ * IBuProcessKeyEventData properties.
++ */
++struct _IBusProcessKeyEventData {
++    /*< public >*/
++    guint keyval;
++    guint keycode;
++    guint state;
++};
++
++/**
++ * IBusExtensionEvent:
++ *
++ * IBusExtensionEvent properties.
++ */
++struct _IBusExtensionEvent {
++    /*< private >*/
++    IBusSerializable parent;
++    IBusExtensionEventPrivate *priv;
++
++    /* instance members */
++    /*< public >*/
++};
++
++struct _IBusExtensionEventClass {
++    /*< private >*/
++    IBusSerializableClass parent;
++
++    /* class members */
++    /*< public >*/
++
++    /*< private >*/
++    /* padding */
++    gpointer pdummy[10];
++};
++
++
++GType              ibus_extension_event_get_type    (void);
++
++/**
++ * ibus_extension_event_new:
++ * @first_property_name: Name of the first property.
++ * @...: the NULL-terminated arguments of the properties and values.
++ *
++ * Create a new #IBusExtensionEvent.
++ *
++ * Returns: A newly allocated #IBusExtensionEvent. E.g.
++ * ibus_extension_event_new ("name", "emoji", "is-enabled", TRUE, NULL);
++ */
++IBusExtensionEvent *ibus_extension_event_new        (const gchar
++                                                           *first_property_name,
++                                                     ...);
++
++/**
++ * ibus_extension_event_get_version:
++ * @event: An #IBusExtensionEvent.
++ *
++ * Returns: Version of #IBusExtensionEvent
++ */
++guint              ibus_extension_event_get_version (IBusExtensionEvent *event);
++
++/**
++ * ibus_extension_event_get_purpose:
++ * @event: An #IBusExtensionEvent.
++ *
++ * Returns: name of the extension for #IBusXEvent
++ */
++const gchar *      ibus_extension_event_get_name    (IBusExtensionEvent *event);
++
++/**
++ * ibus_extension_event_is_enabled:
++ * @event: An #IBusExtensionEvent.
++ *
++ * Returns: %TRUE if the extension is enabled for #IBusExtensionEvent
++ */
++gboolean           ibus_extension_event_is_enabled  (IBusExtensionEvent *event);
++
++/**
++ * ibus_extension_event_is_extension:
++ * @event: An #IBusExtensionEvent.
++ *
++ * Returns: %TRUE if the #IBusExtensionEvent is called by an extension.
++ * %FALSE if the #IBusExtensionEvent is called by an active engine or
++ * panel.
++ * If this value is %TRUE, the event is send to ibus-daemon, an active
++ * engine. If it's %FALSE, the event is sned to ibus-daemon, panels.
++ */
++gboolean           ibus_extension_event_is_extension
++                                                    (IBusExtensionEvent *event);
++
++/**
++ * ibus_extension_event_get_params:
++ * @event: An #IBusExtensionEvent.
++ *
++ * Returns: Parameters to enable the extension for #IBusXEvent
++ */
++const gchar *      ibus_extension_event_get_params  (IBusExtensionEvent *event);
++
++
++
+ typedef enum {
+     IBUS_X_EVENT_NOTHING           = -1,
+     IBUS_X_EVENT_KEY_PRESS         = 0,
+@@ -76,7 +201,7 @@ typedef enum {
+  * IBusXEvent:
+  * @type: event type
+  *
+- * IBusEngine properties.
++ * IBusXEvent properties.
+  */
+ struct _IBusXEvent {
+     /*< private >*/
+diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
+index bf9f98d7..aaba7a4d 100644
+--- a/ui/gtk3/Makefile.am
++++ b/ui/gtk3/Makefile.am
+@@ -78,6 +78,7 @@ AM_VALAFLAGS = \
+ 	--pkg=ibus-1.0 \
+ 	--pkg=config \
+ 	--pkg=xi \
++	--pkg=gdk-wayland \
+ 	--target-glib="$(VALA_TARGET_GLIB_VERSION)" \
+ 	$(NULL)
+ 
+@@ -176,6 +177,7 @@ ibus_ui_emojier_VALASOURCES =                   \
+     emojier.vala                                \
+     iconwidget.vala                             \
+     separator.vala                              \
++    pango.vala                                  \
+     $(NULL)
+ ibus_ui_emojier_SOURCES =                       \
+     $(ibus_ui_emojier_VALASOURCES:.vala=.c)     \
+@@ -213,6 +215,7 @@ ibus_extension_gtk3_VALASOURCES =               \
+     iconwidget.vala                             \
+     keybindingmanager.vala                      \
+     panelbinding.vala                           \
++    pango.vala                                  \
+     $(NULL)
+ ibus_extension_gtk3_SOURCES =                   \
+     $(ibus_extension_gtk3_VALASOURCES:.vala=.c) \
+diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
+index 0c0865f1..cd98c9d7 100644
+--- a/ui/gtk3/emojier.vala
++++ b/ui/gtk3/emojier.vala
+@@ -226,43 +226,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             }
+         }
+     }
+-    private class ETitleLabelBox : Gtk.HeaderBar {
+-        private Gtk.Label m_lang_label;
+-        private Gtk.Label m_title_label;
+-
+-        public ETitleLabelBox(string title) {
+-            GLib.Object(
+-                name : "IBusEmojierTitleLabelBox",
+-                show_close_button: true,
+-                decoration_layout: ":close",
+-                title: title
+-            );
+-            var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+-            set_custom_title(vbox);
+-            m_title_label = new Gtk.Label(title);
+-            m_title_label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE);
+-            vbox.pack_start(m_title_label, true, false, 0);
+-            m_lang_label = new Gtk.Label(null);
+-            m_lang_label.get_style_context().add_class(
+-                    Gtk.STYLE_CLASS_SUBTITLE);
+-            vbox.pack_start(m_lang_label, true, false, 0);
+-
+-            var menu = new GLib.Menu();
+-            menu.append(_("Show emoji variants"), "win.variant");
+-            var menu_button = new Gtk.MenuButton();
+-            menu_button.set_direction(Gtk.ArrowType.NONE);
+-            menu_button.set_valign(Gtk.Align.CENTER);
+-            menu_button.set_menu_model(menu);
+-            menu_button.set_tooltip_text(_("Menu"));
+-            pack_end(menu_button);
+-        }
+-        public new void set_title(string title) {
+-            m_title_label.set_text(title);
+-        }
+-        public void set_lang_label(string str) {
+-            m_lang_label.set_text(str);
+-        }
+-    }
+     private class LoadProgressObject : GLib.Object {
+         public LoadProgressObject() {
+         }
+@@ -275,6 +238,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         BACKWARD,
+     }
+ 
++    public const uint BUTTON_CLOSE_BUTTON = 1000;
++
+     private const uint EMOJI_GRID_PAGE = 10;
+     private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites");
+     private const string EMOJI_CATEGORY_OTHERS = N_("Others");
+@@ -313,11 +278,19 @@ 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;
++    private static string m_warning_message = "";
+ 
+     private ThemedRGBA m_rgba;
+     private Gtk.Box m_vbox;
+-    private ETitleLabelBox m_title;
+     private EEntry m_entry;
++    /* If emojier is emoji category list or Unicode category list,
++     * m_annotation is "" and preedit is also "".
++     * If emojier is candidate mode, m_annotation is an annotation and
++     * get_current_candidate() returns the current emoji.
++     * But the current preedit can be "" in candidate mode in case that
++     * Unicode candidate window has U+0000.
++     */
++    private string m_annotation = "";
+     private string? m_backward;
+     private int m_backward_index = -1;
+     private EScrolledWindow? m_scrolled_window = null;
+@@ -326,8 +299,20 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     private string m_input_context_path = "";
+     private GLib.MainLoop? m_loop;
+     private string? m_result;
+-    private string? m_unicode_point = null;
++    /* If m_candidate_panel_is_visible is true, emojier is candidate mode and
++     * the emoji lookup window is visible.
++     * If m_candidate_panel_is_visible is false, the emoji lookup window is
++     * not visible but the mode is not clear.
++     */
+     private bool m_candidate_panel_is_visible;
++    /* If m_candidate_panel_mode is true, emojier is candidate mode and
++     * it does not depend on whether the window is visible or not.
++     * I.E. the first candidate does not show the lookup window and the
++     * second one shows the window.
++     * If m_candidate_panel_mode is false, emojier is emoji category list or
++     * Unicode category list.
++     */
++    private bool m_candidate_panel_mode;
+     private int m_category_active_index = -1;
+     private IBus.LookupTable m_lookup_table;
+     private Gtk.Label[] m_candidates;
+@@ -337,23 +322,18 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     protected static double m_mouse_x;
+     protected static double m_mouse_y;
+     private Gtk.ProgressBar m_unicode_progress_bar;
++    private uint m_unicode_progress_id;
+     private Gtk.Label m_unicode_percent_label;
+     private double m_unicode_percent;
++    private Gdk.Rectangle m_cursor_location;
++    private bool m_is_up_side_down = false;
++    private uint m_redraw_window_id;
+ 
+     public signal void candidate_clicked(uint index, uint button, uint state);
+ 
+     public IBusEmojier() {
+         GLib.Object(
+-            type : Gtk.WindowType.TOPLEVEL,
+-            events : Gdk.EventMask.KEY_PRESS_MASK |
+-                     Gdk.EventMask.KEY_RELEASE_MASK |
+-                     Gdk.EventMask.BUTTON_PRESS_MASK |
+-                     Gdk.EventMask.BUTTON_RELEASE_MASK,
+-            window_position : Gtk.WindowPosition.CENTER,
+-            icon_name: "ibus-setup",
+-            accept_focus : true,
+-            resizable : true,
+-            focus_visible : true
++            type : Gtk.WindowType.POPUP
+         );
+ 
+         // GLib.ActionEntry accepts const variables only.
+@@ -363,6 +343,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+                 new GLib.Variant.boolean(m_show_emoji_variant));
+         action.activate.connect(check_action_variant_cb);
+         add_action(action);
++        action = new GLib.SimpleAction("close", null);
++        action.activate.connect(action_close_cb);
++        add_action(action);
+         if (m_current_lang_id == null)
+             m_current_lang_id = "en";
+         if (m_emoji_font_family == null)
+@@ -448,14 +431,12 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+                 css_provider,
+                 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ 
+-        m_title = new ETitleLabelBox(_("Emoji Choice"));
+-        set_titlebar(m_title);
+         m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+         add(m_vbox);
+ 
+         m_entry = new EEntry();
+         m_entry.set_placeholder_text(_("Type annotation or choose emoji"));
+-        m_vbox.add(m_entry);
++        //m_vbox.add(m_entry);
+         m_entry.changed.connect(() => {
+             update_candidate_window();
+         });
+@@ -480,10 +461,16 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+                 m_loop.quit();
+         });
+ 
++        size_allocate.connect((w, a) => {
++            adjust_window_position();
++        });
++
+         candidate_clicked.connect((i, b, s) => {
+-            candidate_panel_select_index(i);
++            if (m_input_context_path != "")
++                candidate_panel_select_index(i, b);
+         });
+ 
++
+         if (m_annotation_to_emojis_dict == null) {
+             reload_emoji_dict();
+         }
+@@ -814,6 +801,12 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ 
+ 
+     private void remove_all_children() {
++        if (m_list_box != null) {
++            foreach (Gtk.Widget w in m_list_box.get_children()) {
++                w.destroy();
++            }
++            m_list_box = null;
++        }
+         foreach (Gtk.Widget w in m_vbox.get_children()) {
+             if (w.name == "IBusEmojierEntry" ||
+                 w.name == "IBusEmojierTitleLabelBox") {
+@@ -824,15 +817,40 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    private void clamp_page() {
++        Gtk.ListBoxRow row;
++        if (m_category_active_index >= 0) {
++            row = m_list_box.get_row_at_index(m_category_active_index);
++            m_list_box.select_row(row);
++        } else {
++            row = m_list_box.get_row_at_index(0);
++        }
++        Gtk.Allocation alloc = { 0, 0, 0, 0 };
++        row.get_allocation(out alloc);
++        var adjustment = m_scrolled_window.get_vadjustment();
++        adjustment.clamp_page(alloc.y, alloc.y + alloc.height);
++        return_val_if_fail(m_category_active_index >= 0, false);
++        m_lookup_table.set_cursor_pos((uint)m_category_active_index);
++    }
++
++
+     private void show_category_list() {
++        // Do not call remove_all_children() to work adjustment.clamp_page()
++        // with PageUp/Down.
++        // After show_candidate_panel() is called, m_category_active_index
++        // is saved for Escape key but m_list_box is null by
++        // remove_all_children().
++        if (m_category_active_index >= 0 && m_list_box != null) {
++            var row = m_list_box.get_row_at_index(m_category_active_index);
++            m_list_box.select_row(row);
++            return;
++        }
++        if (m_category_active_index < 0)
++            m_category_active_index = 0;
+         remove_all_children();
+         m_scrolled_window = new EScrolledWindow();
+         set_fixed_size();
+ 
+-        m_title.set_title(_("Emoji Choice"));
+-        string language =
+-            IBus.get_language_name(m_current_lang_id);
+-        m_title.set_lang_label(language);
+         m_vbox.add(m_scrolled_window);
+         Gtk.Viewport viewport = new Gtk.Viewport(null, null);
+         m_scrolled_window.add(viewport);
+@@ -842,53 +860,21 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
+         m_list_box.set_adjustment(adjustment);
+         m_list_box.row_activated.connect((box, gtkrow) => {
+-            m_category_active_index = -1;
++            m_category_active_index = gtkrow.get_index();
+             EBoxRow row = gtkrow as EBoxRow;
+             show_emoji_for_category(row.text);
++            show_all();
+         });
+ 
+-        uint n = 0;
+-        if (m_favorites.length > 0) {
+-            EBoxRow row = new EBoxRow(EMOJI_CATEGORY_FAVORITES);
+-            EPaddedLabelBox widget =
+-                    new EPaddedLabelBox(_(EMOJI_CATEGORY_FAVORITES),
+-                                        Gtk.Align.CENTER);
+-            row.add(widget);
+-            m_list_box.add(row);
+-            if (n++ == m_category_active_index)
+-                m_list_box.select_row(row);
+-        }
+-        GLib.List<unowned string> categories =
+-                m_category_to_emojis_dict.get_keys();
+-        // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
+-        categories.sort((a, b) => {
+-            if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS)
+-                return 1;
+-            else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS)
+-                return -1;
+-            return GLib.strcmp(_(a), _(b));
+-        });
+-        foreach (unowned string category in categories) {
+-            // "Others" category includes next unicode chars and fonts do not support
+-            // the base and varints yet.
+-            if (category == EMOJI_CATEGORY_OTHERS)
+-               continue;
++        uint ncandidates = m_lookup_table.get_number_of_candidates();
++        for (uint i = 0; i < ncandidates; i++) {
++            string category = m_lookup_table.get_candidate(i).text;
+             EBoxRow row = new EBoxRow(category);
+             EPaddedLabelBox widget =
+                     new EPaddedLabelBox(_(category), Gtk.Align.CENTER);
+             row.add(widget);
+             m_list_box.add(row);
+-            if (n++ == m_category_active_index)
+-                m_list_box.select_row(row);
+-        }
+-        if (m_unicode_block_list.length() > 0) {
+-            EBoxRow row = new EBoxRow(EMOJI_CATEGORY_UNICODE);
+-            EPaddedLabelBox widget =
+-                    new EPaddedLabelBox(_(EMOJI_CATEGORY_UNICODE),
+-                                        Gtk.Align.CENTER);
+-            row.add(widget);
+-            m_list_box.add(row);
+-            if (n++ == m_category_active_index)
++            if (i == m_category_active_index)
+                 m_list_box.select_row(row);
+         }
+ 
+@@ -903,6 +889,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     private void show_emoji_for_category(string category) {
+         if (category == EMOJI_CATEGORY_FAVORITES) {
+             m_lookup_table.clear();
++            m_candidate_panel_mode = true;
+             foreach (unowned string favorate in m_favorites) {
+                 IBus.Text text = new IBus.Text.from_string(favorate);
+                 m_lookup_table.append_candidate(text);
+@@ -911,25 +898,26 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         } else if (category == EMOJI_CATEGORY_UNICODE) {
+             m_category_active_index = -1;
+             m_show_unicode = true;
+-            show_unicode_blocks();
++            update_unicode_blocks();
+             return;
+         } else {
+             unowned GLib.SList<unowned string> emojis =
+                     m_category_to_emojis_dict.lookup(category);
+             m_lookup_table.clear();
++            m_candidate_panel_mode = true;
+             foreach (unowned string emoji in emojis) {
+                 IBus.Text text = new IBus.Text.from_string(emoji);
+                 m_lookup_table.append_candidate(text);
+             }
+             m_backward = category;
+         }
++        m_annotation = m_lookup_table.get_candidate(0).text;
+         // Restore the cursor position before the special table of
+         // emoji variants is shown.
+         if (m_backward_index >= 0) {
+             m_lookup_table.set_cursor_pos((uint)m_backward_index);
+             m_backward_index = -1;
+         }
+-        show_candidate_panel();
+     }
+ 
+ 
+@@ -940,18 +928,28 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             IBus.Text text = new IBus.Text.from_string(emoji);
+             m_lookup_table.append_candidate(text);
+         }
+-        show_candidate_panel();
+     }
+ 
+ 
+     private void show_unicode_blocks() {
++        // Do not call remove_all_children() to work adjustment.clamp_page()
++        // with PageUp/Down.
++        // After show_candidate_panel() is called, m_category_active_index
++        // is saved for Escape key but m_list_box is null by
++        // remove_all_children().
++        if (m_category_active_index >= 0 && m_list_box != null) {
++            var row = m_list_box.get_row_at_index(m_category_active_index);
++            m_list_box.select_row(row);
++            return;
++        }
++        if (m_category_active_index < 0)
++            m_category_active_index = 0;
+         m_show_unicode = true;
+         if (m_default_window_width == 0 && m_default_window_height == 0)
+             get_size(out m_default_window_width, out m_default_window_height);
+         remove_all_children();
+         set_fixed_size();
+ 
+-        m_title.set_title(_("Unicode Choice"));
+         EPaddedLabelBox label =
+                 new EPaddedLabelBox(_("Bring back emoji choice"),
+                                     Gtk.Align.CENTER,
+@@ -964,10 +962,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             m_category_active_index = -1;
+             m_show_unicode = false;
+             hide_candidate_panel();
++            show_all();
+             return true;
+         });
+         m_scrolled_window = new EScrolledWindow();
+-        m_title.set_lang_label("");
+         m_vbox.add(m_scrolled_window);
+         Gtk.Viewport viewport = new Gtk.Viewport(null, null);
+         m_scrolled_window.add(viewport);
+@@ -977,9 +975,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
+         m_list_box.set_adjustment(adjustment);
+         m_list_box.row_activated.connect((box, gtkrow) => {
+-            m_category_active_index = -1;
++            m_category_active_index = gtkrow.get_index();
+             EBoxRow row = gtkrow as EBoxRow;
+             show_unicode_for_block(row.text);
++            show_candidate_panel();
+         });
+ 
+         uint n = 0;
+@@ -1007,44 +1006,18 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             m_list_box.unselect_all();
+         m_list_box.invalidate_filter();
+         m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
++        Gtk.ListBoxRow row = m_list_box.get_row_at_index((int)n - 1);
++
++        // If clamp_page() would be called without the allocation signal,
++        // the jumping page could be failed when returns from 
++        // show_unicode_for_block() with Escape key.
++        row.size_allocate.connect((w, a) => {
++            clamp_page();
++        });
+     }
+ 
++
+     private void show_unicode_for_block(string block_name) {
+-        if (!m_loaded_unicode) {
+-            remove_all_children();
+-            set_fixed_size();
+-            m_unicode_progress_bar = new Gtk.ProgressBar();
+-            m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
+-            m_unicode_progress_bar.set_halign(Gtk.Align.CENTER);
+-            m_unicode_progress_bar.set_valign(Gtk.Align.CENTER);
+-            m_vbox.add(m_unicode_progress_bar);
+-            m_unicode_progress_bar.show();
+-            var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
+-            hbox.set_halign(Gtk.Align.CENTER);
+-            hbox.set_valign(Gtk.Align.CENTER);
+-            m_vbox.add(hbox);
+-            var label = new Gtk.Label(_("Loading a Unicode dictionary:"));
+-            hbox.pack_start(label, false, true, 0);
+-            m_unicode_percent_label = new Gtk.Label("");
+-            hbox.pack_start(m_unicode_percent_label, false, true, 0);
+-            hbox.show_all();
+-
+-            m_unicode_progress_object.deserialize_unicode.connect((i, n) => {
+-                m_unicode_percent = (double)i / n;
+-            });
+-            GLib.Timeout.add(100, () => {
+-                m_unicode_progress_bar.set_fraction(m_unicode_percent);
+-                m_unicode_percent_label.set_text(
+-                        "%.0f%%\n".printf(m_unicode_percent * 100));
+-                m_unicode_progress_bar.show();
+-                m_unicode_percent_label.show();
+-                if (m_loaded_unicode) {
+-                    show_unicode_for_block(block_name);
+-                }
+-                return !m_loaded_unicode;
+-            });
+-            return;
+-        }
+         unichar start = 0;
+         unichar end = 0;
+         foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
+@@ -1055,6 +1028,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             }
+         }
+         m_lookup_table.clear();
++        m_candidate_panel_mode = true;
+         for (unichar ch = start; ch < end; ch++) {
+             unowned IBus.UnicodeData? data =
+                     m_unicode_to_data_dict.lookup(ch);
+@@ -1064,7 +1038,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             m_lookup_table.append_candidate(text);
+         }
+         m_backward = block_name;
+-        show_candidate_panel();
++        m_annotation = m_lookup_table.get_candidate(0).text;
+     }
+ 
+ 
+@@ -1091,6 +1065,41 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         prev_button.set_relief(Gtk.ReliefStyle.NONE);
+         prev_button.set_tooltip_text(_("Page Up"));
+ 
++        var menu = new GLib.Menu();
++        menu.append(_("Show emoji variants"), "win.variant");
++        menu.append(_("Close"), "win.close");
++        var menu_button = new Gtk.MenuButton();
++        menu_button.set_direction(Gtk.ArrowType.NONE);
++        menu_button.set_valign(Gtk.Align.CENTER);
++        menu_button.set_menu_model(menu);
++        menu_button.set_relief(Gtk.ReliefStyle.NONE);
++        menu_button.set_tooltip_text(_("Menu"));
++
++        IBus.Text text = this.get_title_text();
++        Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text);
++        Gtk.Label title_label = new Gtk.Label(text.get_text());
++        title_label.set_attributes(attrs);
++
++        Gtk.Button? warning_button = null;
++        if (m_warning_message != "") { 
++            warning_button = new Gtk.Button();
++            warning_button.set_tooltip_text(
++                    _("Click to view a warning message"));
++            warning_button.set_image(new Gtk.Image.from_icon_name(
++                                  "dialog-warning",
++                                  Gtk.IconSize.MENU));
++            warning_button.set_relief(Gtk.ReliefStyle.NONE);
++            warning_button.clicked.connect(() => {
++                Gtk.Label warning_label = new Gtk.Label(m_warning_message);
++                warning_label.set_line_wrap(true);
++                warning_label.set_max_width_chars(40);
++                Gtk.Popover popover = new Gtk.Popover(warning_button);
++                popover.add(warning_label);
++                popover.show_all();
++                popover.popup();
++            });
++        }
++
+         Gtk.Box buttons_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+         Gtk.Label state_label = new Gtk.Label(null);
+         state_label.set_size_request(10, -1);
+@@ -1099,14 +1108,55 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         buttons_hbox.pack_start(state_label, false, true, 0);
+         buttons_hbox.pack_start(prev_button, false, false, 0);
+         buttons_hbox.pack_start(next_button, false, false, 0);
++        buttons_hbox.pack_start(title_label, false, false, 0);
++        if (warning_button != null)
++            buttons_hbox.pack_start(warning_button, false, false, 0);
++        buttons_hbox.pack_end(menu_button, false, false, 0);
+         m_vbox.pack_start(buttons_hbox, false, false, 0);
+         buttons_hbox.show_all();
+     }
+ 
+ 
+-    private bool check_unicode_point() {
+-        string annotation = m_entry.get_text();
+-        m_unicode_point = null;
++    private void show_unicode_progress_bar() {
++        m_unicode_progress_bar = new Gtk.ProgressBar();
++        m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
++        m_unicode_progress_bar.set_halign(Gtk.Align.CENTER);
++        m_unicode_progress_bar.set_valign(Gtk.Align.CENTER);
++        m_vbox.add(m_unicode_progress_bar);
++        m_unicode_progress_bar.show();
++        var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
++        hbox.set_halign(Gtk.Align.CENTER);
++        hbox.set_valign(Gtk.Align.CENTER);
++        m_vbox.add(hbox);
++        var label = new Gtk.Label(_("Loading a Unicode dictionary:"));
++        hbox.pack_start(label, false, true, 0);
++        m_unicode_percent_label = new Gtk.Label("");
++        hbox.pack_start(m_unicode_percent_label, false, true, 0);
++        hbox.show_all();
++
++        m_unicode_progress_object.deserialize_unicode.connect((i, n) => {
++            m_unicode_percent = (double)i / n;
++        });
++        if (m_unicode_progress_id > 0) {
++            GLib.Source.remove(m_unicode_progress_id);
++        }
++        m_unicode_progress_id = GLib.Timeout.add(100, () => {
++            m_unicode_progress_id = 0;
++            m_unicode_progress_bar.set_fraction(m_unicode_percent);
++            m_unicode_percent_label.set_text(
++                    "%.0f%%\n".printf(m_unicode_percent * 100));
++            m_unicode_progress_bar.show();
++            m_unicode_percent_label.show();
++            if (m_loaded_unicode) {
++                show_candidate_panel();
++            }
++            return !m_loaded_unicode;
++        });
++    }
++
++
++    private static string? check_unicode_point(string annotation) {
++        string unicode_point = null;
+         // Add "0x" because uint64.ascii_strtoull() is not accessible
+         // and need to use uint64.parse()
+         var buff = new GLib.StringBuilder("0x");
+@@ -1114,33 +1164,31 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         for (int i = 0; i < annotation.char_count(); i++) {
+             unichar ch = annotation.get_char(i);
+             if (ch == 0)
+-                return false;
++                return null;
+             if (ch.isspace()) {
+                 unichar code = (unichar)uint64.parse(buff.str);
+                 buff.assign("0x");
+                 if (!code.validate())
+-                    return false;
++                    return null;
+                 retval.append(code.to_string());
+                 continue;
+             }
+             if (!ch.isxdigit())
+-                return false;
++                return null;
+             buff.append_unichar(ch);
+         }
+         unichar code = (unichar)uint64.parse(buff.str);
+         if (!code.validate())
+-            return false;
++            return null;
+         retval.append(code.to_string());
+-        m_unicode_point = retval.str;
+-        if (m_unicode_point == null)
+-            return true;
+-        IBus.Text text = new IBus.Text.from_string(m_unicode_point);
+-        m_lookup_table.append_candidate(text);
+-        return true;
++        unicode_point = retval.str;
++        if (unicode_point == null)
++            return null;
++        return unicode_point;
+     }
+ 
+ 
+-    private GLib.SList<string>?
++    private static GLib.SList<string>?
+     lookup_emojis_from_annotation(string annotation) {
+         GLib.SList<string>? total_emojis = null;
+         unowned GLib.SList<string>? sub_emojis = null;
+@@ -1221,19 +1269,19 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         return total_emojis;
+     }
+ 
++
+     private void update_candidate_window() {
+-        string annotation = m_entry.get_text();
++        string annotation = m_annotation;
+         if (annotation.length == 0) {
+-            hide_candidate_panel();
+             m_backward = null;
+             return;
+         }
++        m_lookup_table.clear();
++        m_category_active_index = -1;
+         if (annotation.length > m_emoji_max_seq_len) {
+-            hide_candidate_panel();
+             return;
+         }
+-        // Call check_unicode_point() to get m_unicode_point
+-        check_unicode_point();
++        string? unicode_point = check_unicode_point(annotation);
+         GLib.SList<string>? total_emojis =
+             lookup_emojis_from_annotation(annotation);
+         if (total_emojis == null) {
+@@ -1246,18 +1294,75 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             annotation = annotation.down();
+             total_emojis = lookup_emojis_from_annotation(annotation);
+         }
+-        if (total_emojis == null && m_unicode_point == null) {
+-            hide_candidate_panel();
++        if (total_emojis == null && unicode_point == null) {
+             return;
+         }
+-        m_lookup_table.clear();
+-        // Call check_unicode_point() to update m_lookup_table
+-        check_unicode_point();
++        if (unicode_point != null) {
++            IBus.Text text = new IBus.Text.from_string(unicode_point);
++            m_lookup_table.append_candidate(text);
++        }
+         foreach (unowned string emoji in total_emojis) {
+             IBus.Text text = new IBus.Text.from_string(emoji);
+             m_lookup_table.append_candidate(text);
+         }
+-        show_candidate_panel();
++        m_candidate_panel_is_visible =
++            (m_lookup_table.get_number_of_candidates() > 0) ? true : false;
++        m_candidate_panel_mode = true;
++    }
++
++
++    private void update_category_list() {
++        // Always update m_lookup_table even if the contents are same
++        // because m_category_active_index needs to be kept after
++        // bring back this API from show_emoji_for_category().
++        reset_window_mode();
++        m_lookup_table.clear();
++        IBus.Text text;
++        if (m_favorites.length > 0) {
++            text = new IBus.Text.from_string(EMOJI_CATEGORY_FAVORITES);
++            m_lookup_table.append_candidate(text);
++        }
++        GLib.List<unowned string> categories =
++                m_category_to_emojis_dict.get_keys();
++        // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
++        categories.sort((a, b) => {
++            if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS)
++                return 1;
++            else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS)
++                return -1;
++            return GLib.strcmp(_(a), _(b));
++        });
++        foreach (unowned string category in categories) {
++            // "Others" category includes next unicode chars and fonts do not
++            // support the base and varints yet.
++            if (category == EMOJI_CATEGORY_OTHERS)
++               continue;
++            text = new IBus.Text.from_string(category);
++            m_lookup_table.append_candidate(text);
++        }
++        if (m_unicode_block_list.length() > 0) {
++            text = new IBus.Text.from_string(EMOJI_CATEGORY_UNICODE);
++            m_lookup_table.append_candidate(text);
++        }
++        // Do not set m_category_active_index to 0 here so that
++        // show_category_list() handles it.
++    }
++
++
++    private void update_unicode_blocks() {
++        // Always update m_lookup_table even if the contents are same
++        // because m_category_active_index needs to be kept after
++        // bring back this API from show_emoji_for_category().
++        reset_window_mode();
++        m_lookup_table.clear();
++        m_show_unicode = true;
++        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
++            string name = block.get_name();
++            IBus.Text text = new IBus.Text.from_string(name);
++            m_lookup_table.append_candidate(text);
++        }
++        // Do not set m_category_active_index to 0 here so that
++        // show_unicode_blocks() handles it.
+     }
+ 
+ 
+@@ -1283,27 +1388,27 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         uint page_size = m_lookup_table.get_page_size();
+         uint ncandidates = m_lookup_table.get_number_of_candidates();
+         uint cursor = m_lookup_table.get_cursor_pos();
+-
+         uint page_start_pos = cursor / page_size * page_size;
+         uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates);
++        Gtk.Button? backward_button = null;
+         if (m_backward != null) {
+-            string backward_desc =
+-                    "%s (%u / %u)".printf(_(m_backward), cursor, ncandidates - 1);
++            string backward_desc = _(m_backward);
+             EPaddedLabelBox label =
+                     new EPaddedLabelBox(backward_desc,
+                                         Gtk.Align.CENTER,
+                                         TravelDirection.BACKWARD);
+-            Gtk.Button button = new Gtk.Button();
+-            button.add(label);
+-            m_vbox.add(button);
+-            button.show_all();
+-            button.button_press_event.connect((w, e) => {
++            backward_button = new Gtk.Button();
++            backward_button.add(label);
++            backward_button.button_press_event.connect((w, e) => {
+                 // Bring back to emoji candidate panel in case
+                 // m_show_emoji_variant is enabled and shows variants.
+-                if (m_backward_index >= 0 && m_backward != null)
++                if (m_backward_index >= 0 && m_backward != null) {
+                     show_emoji_for_category(m_backward);
+-                else
++                    show_candidate_panel();
++                } else {
+                     hide_candidate_panel();
++                    show_all();
++                }
+                 return true;
+             });
+         }
+@@ -1385,34 +1490,60 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         }
+         if (n > 0) {
+             m_candidate_panel_is_visible = true;
+-            show_arrow_buttons();
+-            m_vbox.add(grid);
+-            grid.show_all();
+-            string text = m_lookup_table.get_candidate(cursor).text;
+-            unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text);
+-            if (data != null) {
+-                show_emoji_description(data, text);
+-                return;
++            if (!m_is_up_side_down) {
++                show_arrow_buttons();
++                if (backward_button != null) {
++                    m_vbox.add(backward_button);
++                    backward_button.show_all();
++                }
++                m_vbox.add(grid);
++                grid.show_all();
++                show_description();
++                if (!m_loaded_unicode)
++                    show_unicode_progress_bar();
+             }
+-            if (text.char_count() <= 1) {
+-                unichar code = text.get_char();
+-                unowned IBus.UnicodeData? udata =
+-                        m_unicode_to_data_dict.lookup(code);
+-                if (udata != null) {
+-                    show_unicode_description(udata, text);
+-                    return;
++            if (m_is_up_side_down) {
++                if (!m_loaded_unicode)
++                    show_unicode_progress_bar();
++                show_description();
++                m_vbox.add(grid);
++                grid.show_all();
++                if (backward_button != null) {
++                    m_vbox.add(backward_button);
++                    backward_button.show_all();
+                 }
++                show_arrow_buttons();
+             }
+-            EPaddedLabelBox widget = new EPaddedLabelBox(
+-                        _("Description: %s").printf(_("None")),
+-                        Gtk.Align.START);
+-            m_vbox.add(widget);
+-            widget.show_all();
+-            show_code_point_description(text);
+         }
+     }
+ 
+ 
++    private void show_description() {
++        uint cursor = m_lookup_table.get_cursor_pos();
++        string text = m_lookup_table.get_candidate(cursor).text;
++        unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text);
++        if (data != null) {
++            show_emoji_description(data, text);
++            return;
++        }
++        if (text.char_count() <= 1) {
++            unichar code = text.get_char();
++            unowned IBus.UnicodeData? udata =
++                    m_unicode_to_data_dict.lookup(code);
++            if (udata != null) {
++                show_unicode_description(udata, text);
++                return;
++            }
++        }
++        EPaddedLabelBox widget = new EPaddedLabelBox(
++                _("Description: %s").printf(_("None")),
++                Gtk.Align.START);
++        m_vbox.add(widget);
++        widget.show_all();
++        show_code_point_description(text);
++    }
++
++
+     private void show_emoji_description(IBus.EmojiData data,
+                                         string         text) {
+         unowned string description = data.get_description();
+@@ -1473,14 +1604,17 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ 
+ 
+     private void hide_candidate_panel() {
++        hide();
+         m_enter_notify_enable = true;
+-        m_candidate_panel_is_visible = false;
+-        if (m_loop.is_running()) {
+-            if (m_show_unicode)
+-                show_unicode_blocks();
+-            else
+-                show_category_list();
+-        }
++        m_annotation = "";
++        // Call remove_all_children() instead of show_category_list()
++        // so that show_category_list do not remove children with
++        // PageUp/PageDown.
++        remove_all_children();
++        if (m_show_unicode)
++            update_unicode_blocks();
++        else
++            update_category_list();
+     }
+ 
+ 
+@@ -1498,20 +1632,34 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
+-    private void candidate_panel_select_index(uint index) {
++    private void candidate_panel_select_index(uint index,
++                                              uint button) {
++        if (button == BUTTON_CLOSE_BUTTON) {
++            hide();
++            if (m_candidate_panel_mode &&
++                m_lookup_table.get_number_of_candidates() > 0) {
++                // Call remove_all_children() instead of show_category_list()
++                // so that show_category_list do not remove children with
++                // PageUp/PageDown.
++                remove_all_children();
++            }
++            m_result = "";
++            return;
++        }
+         string text = m_lookup_table.get_candidate(index).text;
+         unowned GLib.SList<string>? emojis =
+                 m_emoji_to_emoji_variants_dict.lookup(text);
+         if (m_show_emoji_variant && emojis != null &&
+             m_backward_index < 0) {
+             show_emoji_variants(emojis);
++            show_all();
+         } else {
+             m_result = text;
+-            m_loop.quit();
+-            hide_candidate_panel();
++            hide();
+         }
+     }
+ 
++
+     private void candidate_panel_cursor_down() {
+         enter_notify_disable_with_timer();
+         uint ncandidates = m_lookup_table.get_number_of_candidates();
+@@ -1523,7 +1671,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         } else {
+             m_lookup_table.set_cursor_pos(0);
+         }
+-        show_candidate_panel();
+     }
+ 
+ 
+@@ -1541,7 +1688,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         } else {
+             m_lookup_table.set_cursor_pos(0);
+         }
+-        show_candidate_panel();
+     }
+ 
+ 
+@@ -1558,7 +1704,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         return page_num;
+     }
+ 
++
+     private bool category_list_cursor_move(uint keyval) {
++        return_val_if_fail (m_list_box != null, false);
+         GLib.List<weak Gtk.Widget> list = m_list_box.get_children();
+         int length = (int)list.length();
+         if (length == 0)
+@@ -1600,32 +1748,37 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         var row = m_list_box.get_selected_row();
+         if (row != null)
+             m_list_box.unselect_row(row);
+-        if (m_category_active_index >= 0) {
+-            row = m_list_box.get_row_at_index(m_category_active_index);
+-            m_list_box.select_row(row);
+-        } else {
+-            row = m_list_box.get_row_at_index(0);
+-        }
+-        Gtk.Allocation alloc = { 0, 0, 0, 0 };
+-        row.get_allocation(out alloc);
+-        var adjustment = m_scrolled_window.get_vadjustment();
+-        adjustment.clamp_page(alloc.y, alloc.y + alloc.height);
++        clamp_page ();
+         return true;
+     }
+ 
+ 
+-    private bool key_press_cursor_horizontal(uint keyval,
+-                                             uint modifiers) {
++    public bool has_variants(uint index) {
++        if (index >= m_lookup_table.get_number_of_candidates())
++            return false;
++        string text = m_lookup_table.get_candidate(index).text;
++        unowned GLib.SList<string>? emojis =
++                m_emoji_to_emoji_variants_dict.lookup(text);
++        if (m_show_emoji_variant && emojis != null &&
++            m_backward_index < 0) {
++            show_emoji_variants(emojis);
++            return true;
++        }
++        return false;
++    }
++
++
++    public bool key_press_cursor_horizontal(uint keyval,
++                                            uint modifiers) {
+         assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right);
+ 
+-        uint ncandidates = m_lookup_table.get_number_of_candidates();
+-        if (m_candidate_panel_is_visible && ncandidates > 1) {
++        if (m_candidate_panel_mode &&
++            m_lookup_table.get_number_of_candidates() > 0) {
+             enter_notify_disable_with_timer();
+             if (keyval == Gdk.Key.Left)
+                 m_lookup_table.cursor_up();
+             else if (keyval == Gdk.Key.Right)
+                 m_lookup_table.cursor_down();
+-            show_candidate_panel();
+         } else if (m_entry.get_text().length > 0) {
+             int step = 0;
+             if (keyval == Gdk.Key.Left)
+@@ -1650,8 +1803,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
+-    private bool key_press_cursor_vertical(uint keyval,
+-                                           uint modifiers) {
++    public bool key_press_cursor_vertical(uint keyval,
++                                          uint modifiers) {
+         assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up ||
+                 keyval == Gdk.Key.Page_Down || keyval == Gdk.Key.Page_Up);
+ 
+@@ -1661,8 +1814,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             else if (keyval == Gdk.Key.Up)
+                 keyval = Gdk.Key.Page_Up;
+         }
+-        uint ncandidates = m_lookup_table.get_number_of_candidates();
+-        if (m_candidate_panel_is_visible && ncandidates > 1) {
++        if ((m_candidate_panel_is_visible || m_annotation.length > 0)
++            && m_lookup_table.get_number_of_candidates() > 0) {
+             switch (keyval) {
+             case Gdk.Key.Down:
+                 candidate_panel_cursor_down();
+@@ -1673,12 +1826,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             case Gdk.Key.Page_Down:
+                 enter_notify_disable_with_timer();
+                 m_lookup_table.page_down();
+-                show_candidate_panel();
+                 break;
+             case Gdk.Key.Page_Up:
+                 enter_notify_disable_with_timer();
+                 m_lookup_table.page_up();
+-                show_candidate_panel();
+                 break;
+             }
+         } else {
+@@ -1688,19 +1839,18 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
+-    private bool key_press_cursor_home_end(uint keyval,
+-                                           uint modifiers) {
++    public bool key_press_cursor_home_end(uint keyval,
++                                          uint modifiers) {
+         assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End);
+ 
+         uint ncandidates = m_lookup_table.get_number_of_candidates();
+-        if (m_candidate_panel_is_visible && ncandidates > 1) {
++        if (m_candidate_panel_mode && ncandidates > 0) {
+             enter_notify_disable_with_timer();
+             if (keyval == Gdk.Key.Home) {
+                 m_lookup_table.set_cursor_pos(0);
+             } else if (keyval == Gdk.Key.End) {
+                 m_lookup_table.set_cursor_pos(ncandidates - 1);
+             }
+-            show_candidate_panel();
+             return true;
+         }
+         if (m_entry.get_text().length > 0) {
+@@ -1717,44 +1867,41 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+                             ? true : false);
+             return true;
+         }
+-        if (!m_candidate_panel_is_visible)
+-            return category_list_cursor_move(keyval);
+-        return false;
++        return category_list_cursor_move(keyval);
+     }
+ 
+ 
+-    private bool key_press_escape() {
++    public bool key_press_escape() {
+         if (m_show_unicode) {
+-            if (m_candidate_panel_is_visible) {
+-                m_candidate_panel_is_visible = false;
+-                show_unicode_blocks();
+-                return true;
+-            } else {
++            if (!m_candidate_panel_is_visible) {
+                 m_show_unicode = false;
+                 m_category_active_index = -1;
+-                hide_candidate_panel();
+-                return true;
+             }
++            hide_candidate_panel();
++            return true;
+         } else if (m_backward_index >= 0 && m_backward != null) {
+             show_emoji_for_category(m_backward);
+             return true;
+-        } else if (m_candidate_panel_is_visible) {
+-            hide_candidate_panel();
+-            return true;
+-        } else if (m_entry.get_text().length == 0) {
+-            m_loop.quit();
++        } else if (m_candidate_panel_is_visible && m_backward != null) {
+             hide_candidate_panel();
+             return true;
+         }
+-        m_entry.delete_text(0, -1);
+-        return true;
++        hide();
++        if (m_candidate_panel_mode &&
++            m_lookup_table.get_number_of_candidates() > 0) {
++            // Call remove_all_children() instead of show_category_list()
++            // so that show_category_list do not remove children with
++            // PageUp/PageDown.
++            remove_all_children();
++        }
++        return false;
+     }
+ 
+ 
+-    private bool key_press_enter() {
++    public bool key_press_enter() {
+         if (m_candidate_panel_is_visible) {
+             uint index = m_lookup_table.get_cursor_pos();
+-            candidate_panel_select_index(index);
++            return has_variants(index);
+         } else if (m_category_active_index >= 0) {
+             Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row();
+             EBoxRow row = gtkrow as EBoxRow;
+@@ -1789,13 +1936,111 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    private Gdk.Rectangle get_monitor_geometry() {
++        Gdk.Rectangle monitor_area = { 0, };
++
++        // Use get_monitor_geometry() instead of get_monitor_area().
++        // get_monitor_area() excludes docks, but the lookup window should be
++        // shown over them.
++#if VALA_0_34
++        Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor_at_point(
++                m_cursor_location.x,
++                m_cursor_location.y);
++        monitor_area = monitor.get_geometry();
++#else
++        Gdk.Screen screen = Gdk.Screen.get_default();
++        int monitor_num = screen.get_monitor_at_point(m_cursor_location.x,
++                                                      m_cursor_location.y);
++        screen.get_monitor_geometry(monitor_num, out monitor_area);
++#endif
++        return monitor_area;
++    }
++
++
++    private void adjust_window_position() {
++        Gdk.Point cursor_right_bottom = {
++                m_cursor_location.x + m_cursor_location.width,
++                m_cursor_location.y + m_cursor_location.height
++        };
++
++        Gtk.Allocation allocation;
++        get_allocation(out allocation);
++        Gdk.Point window_right_bottom = {
++            cursor_right_bottom.x + allocation.width,
++            cursor_right_bottom.y + allocation.height
++        };
++
++        Gdk.Rectangle monitor_area = get_monitor_geometry();
++        int monitor_right = monitor_area.x + monitor_area.width;
++        int monitor_bottom = monitor_area.y + monitor_area.height;
++
++        int x, y;
++        if (window_right_bottom.x > monitor_right)
++            x = monitor_right - allocation.width;
++        else
++            x = cursor_right_bottom.x;
++        if (x < 0)
++            x = 0;
++
++        bool changed = false;
++        if (window_right_bottom.y > monitor_bottom) {
++            y = m_cursor_location.y - allocation.height;
++            // Do not up side down in Wayland
++            if (m_input_context_path == "") {
++                changed = (m_is_up_side_down == false);
++                m_is_up_side_down = true;
++            } else {
++                changed = (m_is_up_side_down == true);
++                m_is_up_side_down = false;
++            }
++        } else {
++            y = cursor_right_bottom.y;
++            changed = (m_is_up_side_down == true);
++            m_is_up_side_down = false;
++        }
++        if (y < 0)
++            y = 0;
++
++        move(x, y);
++        if (changed) {
++            if (m_redraw_window_id > 0)
++                GLib.Source.remove(m_redraw_window_id);
++            m_redraw_window_id = GLib.Timeout.add(100, () => {
++                m_redraw_window_id = 0;
++                this.show_all();
++                return false;
++            });
++        }
++    }
++
++
++#if 0
++    private void check_action_variant_cb(Gtk.MenuItem item) {
++        Gtk.CheckMenuItem check = item as Gtk.CheckMenuItem;
++        m_show_emoji_variant = check.get_active();
++        // Redraw emoji candidate panel for m_show_emoji_variant
++        if (m_candidate_panel_is_visible) {
++            // DOTO: queue_draw() does not effect at the real time.
++            this.queue_draw();
++        }
++    }
++#else
+     private void check_action_variant_cb(GLib.SimpleAction action,
+                                          GLib.Variant?     parameter) {
+         m_show_emoji_variant = !action.get_state().get_boolean();
+         action.set_state(new GLib.Variant.boolean(m_show_emoji_variant));
+         // Redraw emoji candidate panel for m_show_emoji_variant
+-        if (m_candidate_panel_is_visible)
+-            show_candidate_panel();
++        if (m_candidate_panel_is_visible) {
++            // DOTO: queue_draw() does not effect at the real time.
++            this.queue_draw();
++        }
++    }
++#endif
++
++
++    private void action_close_cb(GLib.SimpleAction action,
++                                 GLib.Variant?     parameter) {
++        candidate_clicked(0, BUTTON_CLOSE_BUTTON, 0);
+     }
+ 
+ 
+@@ -1842,6 +2087,123 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    public void set_annotation(string annotation) {
++        m_annotation = annotation;
++        remove_all_children();
++        if (annotation.length > 0) {
++            update_candidate_window();
++        } else {
++            if (m_show_unicode)
++                update_unicode_blocks();
++            else
++                update_category_list();
++        }
++    }
++
++
++    public IBus.LookupTable get_one_dimension_lookup_table() {
++        var lookup_table = new IBus.LookupTable(EMOJI_GRID_PAGE, 0, true, true);
++        uint i = 0;
++        for (; i < m_lookup_table.get_number_of_candidates(); i++) {
++            IBus.Text text = new IBus.Text.from_string("");
++            text.copy(m_lookup_table.get_candidate(i));
++            lookup_table.append_candidate(text);
++        }
++        if (i > 0)
++            lookup_table.set_cursor_pos(m_lookup_table.get_cursor_pos());
++        return lookup_table;
++    }
++
++
++    public uint get_number_of_candidates() {
++        return m_lookup_table.get_number_of_candidates();
++    }
++
++
++    public uint get_cursor_pos() {
++        return m_lookup_table.get_cursor_pos();
++    }
++
++
++    public void set_cursor_pos(uint cursor_pos) {
++        m_lookup_table.set_cursor_pos(cursor_pos);
++    }
++
++
++    public string get_current_candidate() {
++        // If category_list mode, do not show the category name on preedit.
++        // If candidate_panel mode, the first space key does not show the
++        // lookup table but the first candidate is avaiable on preedit.
++        if (!m_candidate_panel_mode)
++            return "";
++        uint cursor = m_lookup_table.get_cursor_pos();
++        return m_lookup_table.get_candidate(cursor).text;
++    }
++
++
++    public IBus.Text get_title_text() {
++        var language = _(IBus.get_language_name(m_current_lang_id));
++        uint ncandidates = this.get_number_of_candidates();
++        string main_title = _("Emoji Choice");
++        if (m_show_unicode)
++            main_title = _("Unicode Choice");
++        var text = new IBus.Text.from_string(
++                "%s (%s) (%u / %u)".printf(
++                        main_title,
++                        language,
++                        this.get_cursor_pos() + 1,
++                        ncandidates));
++        int char_count = text.text.char_count();
++        int start_index = -1;
++        for (int i = 0; i < char_count; i++) {
++            if (text.text.utf8_offset(i).has_prefix(language)) {
++                start_index = i;
++                break;
++            }
++        }
++        if (start_index >= 0) {
++            var attr = new IBus.Attribute(
++                    IBus.AttrType.FOREGROUND,
++                    0x808080,
++                    start_index,
++                    start_index + language.char_count());
++            var attrs = new IBus.AttrList();
++            attrs.append(attr);
++            text.set_attributes(attrs);
++        }
++        return text;
++    }
++
++
++#if 0
++    public GLib.SList<string>? get_candidates() {
++        if (m_annotation.length == 0) {
++            return null;
++        }
++        if (m_annotation.length > m_emoji_max_seq_len) {
++            return null;
++        }
++        string? unicode_point = check_unicode_point(m_annotation);
++        GLib.SList<string>? total_emojis =
++            lookup_emojis_from_annotation(m_annotation);
++        if (total_emojis == null) {
++            /* Users can type title strings against lower case.
++             * E.g. "Smile" against "smile"
++             * But the dictionary has the case sensitive annotations.
++             * E.g. ":D" and ":q"
++             * So need to call lookup_emojis_from_annotation() twice.
++             */
++            string lower_annotation = m_annotation.down();
++            total_emojis = lookup_emojis_from_annotation(lower_annotation);
++        }
++        if (unicode_point != null)
++            total_emojis.prepend(unicode_point);
++        return total_emojis;
++    }
++#endif
++
++
++#if 0
+     public string run(string    input_context_path,
+                       Gdk.Event event) {
+         assert (m_loop == null);
+@@ -1915,12 +2277,34 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ 
+         return m_result;
+     }
++#endif
+ 
+ 
+     /* override virtual functions */
+-    public override void show() {
+-        base.show();
+-        set_focus_visible(true);
++    public override void show_all() {
++        base.show_all();
++        if (m_candidate_panel_mode)
++            show_candidate_panel();
++        else if (m_show_unicode)
++            show_unicode_blocks();
++        else
++            show_category_list();
++    }
++
++
++    public override void hide() {
++        base.hide();
++        m_candidate_panel_is_visible = false;
++        // m_candidate_panel_mode is not false in when you type something
++        // during enabling the candidate panel.
++        if (m_redraw_window_id > 0) {
++            GLib.Source.remove(m_redraw_window_id);
++            m_redraw_window_id = 0;
++        }
++        if (m_unicode_progress_id > 0) {
++            GLib.Source.remove(m_unicode_progress_id);
++            m_unicode_progress_id = 0;
++        }
+     }
+ 
+ 
+@@ -1935,11 +2319,16 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         switch (keyval) {
+         case Gdk.Key.Escape:
+             if (key_press_escape())
+-                return true;
+-            break;
++                show_all();
++            return true;
+         case Gdk.Key.Return:
+         case Gdk.Key.KP_Enter:
+-            key_press_enter();
++            if (key_press_enter()) {
++                show_all();
++            } else {
++                m_result = get_current_candidate();
++                hide();
++            }
+             return true;
+         case Gdk.Key.BackSpace:
+             if (m_entry.get_text().length > 0) {
+@@ -1977,42 +2366,49 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             }
+             else {
+                 category_list_cursor_move(Gdk.Key.Down);
++                show_all();
+             }
+             return true;
+         case Gdk.Key.Right:
+         case Gdk.Key.KP_Right:
+             key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Left:
+         case Gdk.Key.KP_Left:
+             key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Down:
+         case Gdk.Key.KP_Down:
+             key_press_cursor_vertical(Gdk.Key.Down, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Up:
+         case Gdk.Key.KP_Up:
+             key_press_cursor_vertical(Gdk.Key.Up, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Page_Down:
+         case Gdk.Key.KP_Page_Down:
+             key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Page_Up:
+         case Gdk.Key.KP_Page_Up:
+             key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers);
++            show_all();
+             return true;
+         case Gdk.Key.Home:
+         case Gdk.Key.KP_Home:
+-            if (key_press_cursor_home_end(Gdk.Key.Home, modifiers))
+-                return true;
+-            break;
++            key_press_cursor_home_end(Gdk.Key.Home, modifiers);
++            show_all();
++            return true;
+         case Gdk.Key.End:
+         case Gdk.Key.KP_End:
+-            if (key_press_cursor_home_end(Gdk.Key.End, modifiers))
+-                return true;
+-            break;
++            key_press_cursor_home_end(Gdk.Key.End, modifiers);
++            show_all();
++            return true;
+         case Gdk.Key.Insert:
+         case Gdk.Key.KP_Insert:
+             GLib.Signal.emit_by_name(m_entry, "toggle-overwrite");
+@@ -2023,26 +2419,30 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             switch (keyval) {
+             case Gdk.Key.f:
+                 key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
++                show_all();
+                 return true;
+             case Gdk.Key.b:
+                 key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
++                show_all();
+                 return true;
+             case Gdk.Key.n:
+             case Gdk.Key.N:
+                 key_press_cursor_vertical(Gdk.Key.Down, modifiers);
++                show_all();
+                 return true;
+             case Gdk.Key.p:
+             case Gdk.Key.P:
+                 key_press_cursor_vertical(Gdk.Key.Up, modifiers);
++                show_all();
+                 return true;
+             case Gdk.Key.h:
+-                if (key_press_cursor_home_end(Gdk.Key.Home, modifiers))
+-                    return true;
+-                break;
++                key_press_cursor_home_end(Gdk.Key.Home, modifiers);
++                show_all();
++                return true;
+             case Gdk.Key.e:
+-                if (key_press_cursor_home_end(Gdk.Key.End, modifiers))
+-                    return true;
+-                break;
++                key_press_cursor_home_end(Gdk.Key.End, modifiers);
++                show_all();
++                return true;
+             case Gdk.Key.u:
+                 if (m_entry.get_text().length > 0) {
+                     GLib.Signal.emit_by_name(m_entry,
+@@ -2103,14 +2503,41 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    public void set_input_context_path(string input_context_path) {
++        m_input_context_path = input_context_path;
++        if (input_context_path == "") {
++            m_warning_message = _("" +
++                "Failed to get the current text application. " +
++                "Please re-focus your application. E.g. Press Esc key " +
++                "several times to release the emoji typing mode, " +
++                "click your desktop and click your text application again."
++            );
++        } else {
++            m_warning_message = "";
++        }
++    }
++
++
+     public string get_selected_string() {
+         return m_result;
+     }
+ 
+ 
++    private void reset_window_mode() {
++        m_backward_index = -1;
++        m_backward = null;
++        m_candidate_panel_is_visible = false;
++        m_candidate_panel_mode = false;
++        // Do not clear m_lookup_table to work with space key later.
++    }
++
++
+     public void reset() {
++        reset_window_mode();
+         m_input_context_path = "";
+         m_result = null;
++        m_category_active_index = -1;
++        m_show_unicode = false;
+     }
+ 
+ 
+@@ -2145,6 +2572,23 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    public void set_cursor_location(int x,
++                                    int y,
++                                    int width,
++                                    int height) {
++        Gdk.Rectangle location = Gdk.Rectangle(){
++            x = x, y = y, width = width, height = height };
++        if (m_cursor_location == location)
++            return;
++        m_cursor_location = location;
++    }
++
++
++    public bool is_candidate_panel_mode() {
++        return m_candidate_panel_mode;
++    }
++
++
+     public static bool has_loaded_emoji_dict() {
+         if (m_emoji_to_data_dict == null)
+             return false;
+@@ -2165,6 +2609,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    public static string get_annotation_lang() {
++        return m_current_lang_id;
++    }
++
+     public static void set_emoji_font(string? emoji_font) {
+         return_if_fail(emoji_font != null && emoji_font != "");
+         Pango.FontDescription font_desc =
+@@ -2182,18 +2630,21 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         m_has_partial_match = has_partial_match;
+     }
+ 
++
+     public static void set_partial_match_length(int length) {
+         if (length < 1)
+             return;
+         m_partial_match_length = length;
+     }
+ 
++
+     public static void set_partial_match_condition(int condition) {
+         if (condition < 0)
+             return;
+         m_partial_match_condition = condition;
+     }
+ 
++
+     public static void set_favorites(string[]? unowned_favorites,
+                                      string[]? unowned_favorite_annotations) {
+         m_favorites = {};
+diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
+index 9506a945..787d448f 100644
+--- a/ui/gtk3/emojierapp.vala
++++ b/ui/gtk3/emojierapp.vala
+@@ -28,8 +28,9 @@ int partial_match_condition = -1;
+ 
+ public class EmojiApplication : Gtk.Application {
+     private IBusEmojier? m_emojier;
+-    GLib.Settings m_settings_emoji =
++    private GLib.Settings m_settings_emoji =
+             new GLib.Settings("org.freedesktop.ibus.panel.emoji");
++    private ApplicationCommandLine? m_command_line = null;
+ 
+ 
+     private EmojiApplication() {
+@@ -40,25 +41,39 @@ public class EmojiApplication : Gtk.Application {
+ 
+ 
+     private void show_dialog(ApplicationCommandLine command_line) {
+-        m_emojier = new IBusEmojier();
+-        // For title handling in gnome-shell
+-        add_window(m_emojier);
+-        Gdk.Event event = Gtk.get_current_event();
+-        // Plasma and GNOME3 desktop returns null event
+-        if (event == null) {
+-            event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
+-            event.key.time = Gdk.CURRENT_TIME;
+-            // event.get_seat() refers event.any.window
+-            event.key.window = Gdk.get_default_root_window();
+-            event.key.window.ref();
++        m_command_line = command_line;
++        m_emojier.reset();
++        m_emojier.set_annotation("");
++        m_emojier.show_all();
++    }
++
++
++    public void candidate_clicked_lookup_table(uint index,
++                                               uint button,
++                                               uint state) {
++        if (m_command_line == null)
++            return;
++        if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) {
++            m_emojier.hide();
++            m_command_line.print("%s\n", _("Canceled to choose an emoji."));
++            m_command_line = null;
++            return;
+         }
+-        string emoji = m_emojier.run("", event);
+-        remove_window(m_emojier);
+-        if (emoji == null) {
+-            m_emojier = null;
+-            command_line.print("%s\n", _("Canceled to choose an emoji."));
++        if (m_emojier == null)
++            return;
++        bool show_candidate = false;
++        uint ncandidates = m_emojier.get_number_of_candidates();
++        if (ncandidates > 0 && ncandidates >= index) {
++            m_emojier.set_cursor_pos(index);
++            show_candidate = m_emojier.has_variants(index);
++        } else {
++            return;
++        }
++        if (show_candidate) {
+             return;
+         }
++        string emoji = m_emojier.get_current_candidate();
++        m_emojier.hide();
+         Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
+         clipboard.set_text(emoji, -1);
+         clipboard.store();
+@@ -75,9 +90,8 @@ public class EmojiApplication : Gtk.Application {
+             emojier_favorites += emoji;
+             m_settings_emoji.set_strv("favorites", emojier_favorites);
+         }
+-
+-        m_emojier = null;
+-        command_line.print("%s\n", _("Copied an emoji to your clipboard."));
++        m_command_line.print("%s\n", _("Copied an emoji to your clipboard."));
++        m_command_line = null;
+     }
+ 
+ 
+@@ -88,7 +102,7 @@ public class EmojiApplication : Gtk.Application {
+     }
+ 
+ 
+-    private int _command_line (ApplicationCommandLine command_line) {
++    private int _command_line(ApplicationCommandLine command_line) {
+         // Set default font size
+         IBusEmojier.set_emoji_font(m_settings_emoji.get_string("font"));
+ 
+@@ -181,13 +195,22 @@ public class EmojiApplication : Gtk.Application {
+ 
+         IBusEmojier.load_unicode_dict();
+ 
++        if (m_emojier == null) {
++            m_emojier = new IBusEmojier();
++            // For title handling in gnome-shell
++            add_window(m_emojier);
++            m_emojier.candidate_clicked.connect((i, b, s) => {
++                candidate_clicked_lookup_table(i, b, s);
++            });
++        }
++
+         activate_dialog(command_line);
+ 
+         return Posix.EXIT_SUCCESS;
+     }
+ 
+ 
+-    public override int command_line (ApplicationCommandLine command_line) {
++    public override int command_line(ApplicationCommandLine command_line) {
+         // keep the application running until we are done with this commandline
+         this.hold();
+         int result = _command_line(command_line);
+@@ -196,6 +219,13 @@ public class EmojiApplication : Gtk.Application {
+     }
+ 
+ 
++    public override void shutdown() {
++        base.shutdown();
++        remove_window(m_emojier);
++        m_emojier = null;
++    }
++
++
+     public static int main (string[] args) {
+         GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE,
+                                  Config.GLIB_LOCALE_DIR);
+diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala
+index 7d6d76e7..c729fd7e 100644
+--- a/ui/gtk3/extension.vala
++++ b/ui/gtk3/extension.vala
+@@ -50,20 +50,20 @@ class ExtensionGtk : Gtk.Application {
+                                     "org.freedesktop.DBus",
+                                     "NameAcquired",
+                                     "/org/freedesktop/DBus",
+-                                    IBus.SERVICE_PANEL_EXTENSION,
++                                    IBus.SERVICE_PANEL_EXTENSION_EMOJI,
+                                     DBusSignalFlags.NONE,
+                                     bus_name_acquired_cb);
+         connection.signal_subscribe("org.freedesktop.DBus",
+                                     "org.freedesktop.DBus",
+                                     "NameLost",
+                                     "/org/freedesktop/DBus",
+-                                    IBus.SERVICE_PANEL_EXTENSION,
++                                    IBus.SERVICE_PANEL_EXTENSION_EMOJI,
+                                     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);
++        m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION_EMOJI, flags);
+     }
+ 
+ 
+diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
+index d9238c89..4c3b00ca 100644
+--- a/ui/gtk3/panel.vala
++++ b/ui/gtk3/panel.vala
+@@ -1148,26 +1148,15 @@ class Panel : IBus.PanelService {
+ #if EMOJI_DICT
+             item = new Gtk.MenuItem.with_label(_("Emoji Choice"));
+             item.activate.connect((i) => {
+-                Gdk.Event event = Gtk.get_current_event();
+-                if (event == null) {
+-                    event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
+-                    event.key.time = Gdk.CURRENT_TIME;
+-                    // event.get_seat() refers event.any.window
+-                    event.key.window = Gdk.get_default_root_window();
+-                    event.key.window.ref();
+-                }
+-                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())
++                IBus.ExtensionEvent event = new IBus.ExtensionEvent(
++                        "name", "emoji", "is-enabled", true,
++                        "params", "category-list");
++                /* new GLib.Variant("(sv)", "emoji", event.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.
++                 * the purpose to IBus.ExtensionEvent above.
+                  */
+-                panel_extension(xevent.serialize_object());
++                panel_extension(event);
+             });
+             m_sys_menu.append(item);
+ #endif
+diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
+index 581f721e..52b78c17 100644
+--- a/ui/gtk3/panelbinding.vala
++++ b/ui/gtk3/panelbinding.vala
+@@ -21,7 +21,193 @@
+  * USA
+  */
+ 
++class Preedit : Gtk.Window {
++    private Gtk.Label m_extension_preedit_text;
++    private Gtk.Label m_extension_preedit_emoji;
++    private IBus.Text? m_engine_preedit_text;
++    private bool m_engine_preedit_text_show;
++    private uint m_engine_preedit_cursor_pos;
++    private string m_prefix = "@";
++    private bool m_is_shown = true;
++
++
++    public Preedit() {
++        GLib.Object(
++            name : "IBusPreedit",
++            type: Gtk.WindowType.POPUP
++        );
++        m_extension_preedit_text  = new Gtk.Label("");
++        m_extension_preedit_emoji  = new Gtk.Label("");
++    }
++
++
++    public new void hide() {
++        reset();
++        base.hide();
++        m_is_shown = false;
++    }
++
++
++    public bool is_shown() {
++        return m_is_shown;
++    }
++
++
++    public void reset() {
++        set_emoji("");
++        set_text("");
++        resize(1, 1);
++        m_is_shown = true;
++    }
++
++    public void append_text(string text) {
++        if (text.length == 0)
++            return;
++        string total = m_extension_preedit_text.get_text();
++        total += text;
++        m_extension_preedit_text.set_text(total);
++    }
++
++
++    public string get_text() {
++        return m_extension_preedit_text.get_text();
++    }
++
++
++    public void set_text(string text) {
++        m_extension_preedit_text.set_text(text);
++    }
++
++
++    public string get_emoji() {
++        return m_extension_preedit_emoji.get_text();
++    }
++
++
++    public void set_emoji(string text) {
++        m_extension_preedit_emoji.set_text(text);
++    }
++
++
++    public bool backspace() {
++        string total = m_extension_preedit_emoji.get_text();
++        if (total.length > 0) {
++            m_extension_preedit_emoji.set_text("");
++            resize(1, 1);
++            return false;
++        }
++        total = m_extension_preedit_text.get_text();
++        int char_count = total.char_count();
++        if (char_count == 0)
++            return true;
++        total = total[0:total.index_of_nth_char(char_count - 1)];
++        resize(1, 1);
++        m_extension_preedit_text.set_text(total);
++        if (total.length == 0)
++            resize(1, 1);
++        return true;
++    }
++
++
++    private string get_extension_text () {
++        string extension_text = m_extension_preedit_emoji.get_text();
++        if (extension_text.length == 0)
++            extension_text = m_extension_preedit_text.get_text();
++        return m_prefix + extension_text;
++    }
++
++
++    private void set_preedit_color(IBus.Text text,
++                                   uint start_index,
++                                   uint end_index) {
++        text.append_attribute(IBus.AttrType.UNDERLINE,
++                              IBus.AttrUnderline.SINGLE,
++                              start_index, (int)end_index);
++    }
++
++
++    public IBus.Text get_engine_preedit_text() {
++        string extension_text = get_extension_text();
++        uint char_count = extension_text.char_count();
++        IBus.Text retval;
++        if (m_engine_preedit_text == null || !m_engine_preedit_text_show) {
++            retval = new IBus.Text.from_string(extension_text);
++            set_preedit_color(retval, 0, char_count);
++            return retval;
++        }
++        retval = new IBus.Text.from_string(
++                extension_text + m_engine_preedit_text.get_text());
++        set_preedit_color(retval, 0, char_count);
++
++        unowned IBus.AttrList attrs = m_engine_preedit_text.get_attributes();
++
++        if (attrs == null)
++            return retval;
++
++        int i = 0;
++        while (true) {
++            IBus.Attribute attr = attrs.get(i++);
++            if (attr == null)
++                break;
++            long start_index = attr.start_index;
++            long end_index = attr.end_index;
++            if (start_index < 0)
++                start_index = 0;
++            if (end_index < 0)
++                end_index = m_engine_preedit_text.get_length();
++            retval.append_attribute(attr.type, attr.value,
++                                    char_count + (uint)start_index,
++                                    (int)char_count + (int)end_index);
++        }
++        return retval;
++    }
++
++
++    public void set_engine_preedit_text(IBus.Text? text) {
++        m_engine_preedit_text = text;
++    }
++
++
++    public void show_engine_preedit_text() {
++        m_engine_preedit_text_show = true;
++    }
++
++
++    public void hide_engine_preedit_text() {
++        m_engine_preedit_text_show = false;
++    }
++
++
++    public uint get_engine_preedit_cursor_pos() {
++        return get_extension_text().char_count() + m_engine_preedit_cursor_pos;
++    }
++
++
++    public void set_engine_preedit_cursor_pos(uint cursor_pos) {
++        m_engine_preedit_cursor_pos = cursor_pos;
++    }
++
++
++    public IBus.Text get_commit_text() {
++        string extension_text = m_extension_preedit_emoji.get_text();
++        if (extension_text.length == 0)
++            extension_text = m_extension_preedit_text.get_text();
++        return new IBus.Text.from_string(extension_text);
++    }
++
++
++    public void set_extension_name(string extension_name) {
++        if (extension_name.length == 0)
++            m_prefix = "@";
++        else
++            m_prefix = extension_name[0:1];
++    }
++}
++
++
+ class PanelBinding : IBus.PanelService {
++    private bool m_is_wayland;
++    private bool m_wayland_lookup_table_is_visible;
+     private IBus.Bus m_bus;
+     private Gtk.Application m_application;
+     private GLib.Settings m_settings_panel = null;
+@@ -38,18 +224,26 @@ class PanelBinding : IBus.PanelService {
+     private bool m_loaded_emoji = false;
+     private bool m_load_unicode_at_startup;
+     private bool m_loaded_unicode = false;
++    private bool m_enable_extension;
++    private string m_extension_name = "";
++    private Preedit m_preedit;
+ 
+     public PanelBinding(IBus.Bus bus,
+                         Gtk.Application application) {
+         GLib.assert(bus.is_connected());
+         // Chain up base class constructor
+         GLib.Object(connection : bus.get_connection(),
+-                    object_path : IBus.PATH_PANEL_EXTENSION);
++                    object_path : IBus.PATH_PANEL_EXTENSION_EMOJI);
++
++        Type instance_type = Gdk.Display.get_default().get_type();
++        Type wayland_type = typeof(GdkWayland.Display);
++        m_is_wayland = instance_type.is_a(wayland_type);
+ 
+         m_bus = bus;
+         m_application = application;
+ 
+         init_settings();
++        m_preedit = new Preedit();
+     }
+ 
+ 
+@@ -69,12 +263,20 @@ class PanelBinding : IBus.PanelService {
+                                               ref m_css_provider);
+         });
+ 
++        m_settings_emoji.changed["unicode-hotkey"].connect((key) => {
++                set_emoji_hotkey();
++        });
++
+         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["hotkey"].connect((key) => {
++                set_emoji_hotkey();
++        });
++
+         m_settings_emoji.changed["favorites"].connect((key) => {
+                 set_emoji_favorites();
+         });
+@@ -109,6 +311,54 @@ class PanelBinding : IBus.PanelService {
+     }
+ 
+ 
++    private unowned
++    IBus.ProcessKeyEventData? parse_accelerator(string accelerator) {
++        IBus.ProcessKeyEventData key = {};
++        uint keysym = 0;
++        IBus.ModifierType modifiers = 0;
++        IBus.accelerator_parse(accelerator,
++                out keysym, out modifiers);
++        if (keysym == 0U && modifiers == 0) {
++            warning("Failed to parse shortcut key '%s'".printf(accelerator));
++            return null;
++        }
++        if ((modifiers & IBus.ModifierType.SUPER_MASK) != 0) {
++            modifiers ^= IBus.ModifierType.SUPER_MASK;
++            modifiers |= IBus.ModifierType.MOD4_MASK;
++        }
++        key.keyval = keysym;
++        key.state = modifiers;
++        return key;
++    }
++
++
++    private void set_emoji_hotkey() {
++        IBus.ProcessKeyEventData[] emoji_keys = {};
++        IBus.ProcessKeyEventData key;
++        string[] accelerators = m_settings_emoji.get_strv("hotkey");
++        foreach (var accelerator in accelerators) {
++            key = parse_accelerator(accelerator);
++            emoji_keys += key;
++        }
++
++        /* Since {} is not allocated, parse_accelerator() should be unowned. */
++        key = {};
++        emoji_keys += key;
++
++        IBus.ProcessKeyEventData[] unicode_keys = {};
++        accelerators = m_settings_emoji.get_strv("unicode-hotkey");
++        foreach (var accelerator in accelerators) {
++            key = parse_accelerator(accelerator);
++            unicode_keys += key;
++        }
++        key = {};
++        unicode_keys += key;
++
++        panel_extension_register_keys("emoji", emoji_keys,
++                                      "unicode", unicode_keys);
++    }
++
++
+     private void set_emoji_favorites() {
+         m_emojier_favorites = m_settings_emoji.get_strv("favorites");
+         IBusEmojier.set_favorites(
+@@ -159,6 +409,7 @@ class PanelBinding : IBus.PanelService {
+ 
+     public void load_settings() {
+ 
++        set_emoji_hotkey();
+         set_load_emoji_at_startup();
+         set_load_unicode_at_startup();
+         BindingCommon.set_custom_font(m_settings_panel,
+@@ -181,36 +432,37 @@ class PanelBinding : IBus.PanelService {
+             GLib.Source.remove(m_emojier_set_emoji_lang_id);
+             m_emojier_set_emoji_lang_id = 0;
+         }
+-        m_application = null;
+-    }
+-
+-
+-    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();
+-        // For title handling in gnome-shell
+-        m_application.add_window(m_emojier);
+-        string emoji = m_emojier.run(m_real_current_context_path, event);
+-        m_application.remove_window(m_emojier);
+-        if (emoji == null) {
++        if (m_emojier != null) {
++            m_application.remove_window(m_emojier);
+             m_emojier = null;
+-            return;
+         }
+-        this.emojier_focus_commit();
++        m_application = null;
+     }
+ 
+ 
+-    private void handle_emoji_typing(Gdk.Event event) {
+-        if (m_emojier != null && m_emojier.is_running()) {
+-            m_emojier.present_centralize(event);
++    private void commit_text_update_favorites(IBus.Text text) {
++        commit_text(text);
++        IBus.ExtensionEvent event = new IBus.ExtensionEvent(
++                    "name", m_extension_name,
++                    "is-enabled", false,
++                    "is-extension", true);
++        panel_extension(event);
++        string committed_string = text.text;
++        string preedit_string = m_preedit.get_text();
++        m_preedit.hide();
++        if (preedit_string == committed_string)
+             return;
++        bool has_favorite = false;
++        foreach (unowned string favorite in m_emojier_favorites) {
++            if (favorite == committed_string) {
++                has_favorite = true;
++                break;
++            }
++        }
++        if (!has_favorite) {
++            m_emojier_favorites += committed_string;
++            m_settings_emoji.set_strv("favorites", m_emojier_favorites);
+         }
+-        show_emojier(event);
+     }
+ 
+ 
+@@ -223,19 +475,8 @@ class PanelBinding : IBus.PanelService {
+             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);
+-            }
++            commit_text_update_favorites(text);
++            m_emojier.reset();
+             return true;
+         }
+ 
+@@ -249,8 +490,7 @@ class PanelBinding : IBus.PanelService {
+         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()) {
++            prev_context_path != "") {
+             var context = GLib.MainContext.default();
+             if (m_emojier_focus_commit_text_id > 0 &&
+                 context.find_source_by_id(m_emojier_focus_commit_text_id)
+@@ -277,6 +517,243 @@ class PanelBinding : IBus.PanelService {
+     }
+ 
+ 
++    private bool key_press_escape() {
++        if (is_emoji_lookup_table()) {
++            bool show_candidate = m_emojier.key_press_escape();
++            convert_preedit_text();
++            return show_candidate;
++        }
++        if (m_preedit.get_emoji() != "") {
++            m_preedit.set_emoji("");
++            string annotation = m_preedit.get_text();
++            m_emojier.set_annotation(annotation);
++            return false;
++        }
++        m_enable_extension = false;
++        hide_emoji_lookup_table();
++        m_preedit.hide();
++        IBus.ExtensionEvent event = new IBus.ExtensionEvent(
++                "name", m_extension_name,
++                "is-enabled", false,
++                "is-extension", true);
++        panel_extension(event);
++        return false;
++    }
++
++
++    private bool key_press_enter() {
++        if (m_extension_name != "unicode" && is_emoji_lookup_table()) {
++            // Check if variats exist
++            if (m_emojier.key_press_enter())
++                return true;
++        }
++        IBus.Text text = m_preedit.get_commit_text();
++        commit_text_update_favorites(text);
++        return false;
++    }
++
++
++    private void convert_preedit_text() {
++        if (m_emojier.get_number_of_candidates() > 0)
++            m_preedit.set_emoji(m_emojier.get_current_candidate());
++        else
++            m_preedit.set_emoji("");
++    }
++
++
++    private bool key_press_space() {
++        bool show_candidate = false;
++        if (m_preedit.get_emoji() != "") {
++            m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0);
++            show_candidate = true;
++        } else {
++            string annotation = m_preedit.get_text();
++            if (annotation.length == 0) {
++                show_candidate = true;
++                if (is_emoji_lookup_table())
++                    m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0);
++            } else {
++                m_emojier.set_annotation(annotation);
++            }
++        }
++        convert_preedit_text();
++        return show_candidate;
++    }
++
++
++    private bool key_press_cursor_horizontal(uint keyval,
++                                             uint modifiers) {
++        if (is_emoji_lookup_table()) {
++            m_emojier.key_press_cursor_horizontal(keyval, modifiers);
++            convert_preedit_text();
++            return true;
++        }
++        return false;
++    }
++
++
++    private bool key_press_cursor_vertical(uint keyval,
++                                           uint modifiers) {
++        if (is_emoji_lookup_table()) {
++            m_emojier.key_press_cursor_vertical(keyval, modifiers);
++            convert_preedit_text();
++            return true;
++        }
++        return false;
++    }
++
++
++    private bool key_press_cursor_home_end(uint keyval,
++                                           uint modifiers) {
++        if (is_emoji_lookup_table()) {
++            m_emojier.key_press_cursor_home_end(keyval, modifiers);
++            convert_preedit_text();
++            return true;
++        }
++        return false;
++    }
++
++
++    private bool key_press_control_keyval(uint keyval,
++                                          uint modifiers) {
++        bool show_candidate = false;
++        switch(keyval) {
++        case Gdk.Key.f:
++            show_candidate = key_press_cursor_horizontal(Gdk.Key.Right,
++                                                         modifiers);
++            break;
++        case Gdk.Key.b:
++            show_candidate = key_press_cursor_horizontal(Gdk.Key.Left,
++                                                         modifiers);
++            break;
++        case Gdk.Key.n:
++        case Gdk.Key.N:
++            show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers);
++            break;
++        case Gdk.Key.p:
++        case Gdk.Key.P:
++            show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers);
++            break;
++        case Gdk.Key.h:
++            show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers);
++            break;
++        case Gdk.Key.e:
++            show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers);
++            break;
++        case Gdk.Key.u:
++            m_preedit.reset();
++            m_emojier.set_annotation("");
++            hide_emoji_lookup_table();
++            break;
++        case Gdk.Key.C:
++        case Gdk.Key.c:
++            if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
++                if (!m_is_wayland && m_emojier != null &&
++                    m_emojier.get_number_of_candidates() > 0) {
++                    var text = m_emojier.get_current_candidate();
++                    Gtk.Clipboard clipboard =
++                            Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
++                    clipboard.set_text(text, -1);
++                    clipboard.store();
++                }
++                show_candidate = is_emoji_lookup_table();
++            }
++            break;
++        default:
++            show_candidate = is_emoji_lookup_table();
++            break;
++        }
++        return show_candidate;
++    }
++
++
++    private void hide_wayland_lookup_table() {
++        m_wayland_lookup_table_is_visible = false;
++        var text = new IBus.Text.from_string("");
++        update_auxiliary_text_received(text, false);
++        update_lookup_table_received(
++                new IBus.LookupTable(1, 0, false, true),
++                false);
++    }
++
++
++    private void show_wayland_lookup_table(IBus.Text text) {
++        m_wayland_lookup_table_is_visible = true;
++        var table = m_emojier.get_one_dimension_lookup_table();
++        uint ncandidates = table.get_number_of_candidates();
++        update_auxiliary_text_received(
++                text,
++                ncandidates > 0 ? true : false);
++        update_lookup_table_received(
++                table,
++                ncandidates > 0 ? true : false);
++    }
++
++
++    private bool is_visible_wayland_lookup_table() {
++        return m_wayland_lookup_table_is_visible;
++    }
++
++
++    private void hide_emoji_lookup_table() {
++        if (m_emojier == null)
++            return;
++        if (m_is_wayland)
++            hide_wayland_lookup_table();
++        else
++            m_emojier.hide();
++    }
++
++
++    private void show_emoji_lookup_table() {
++        /* Emojier category_list is shown in both Xorg and Wayland
++         * because the annotation information is useful but the Wayland lookup
++         * window is alway one dimension. So the category_list is shown
++         * when the user annotation is null.
++         */
++        if (m_is_wayland && m_preedit.get_text() != "") {
++            var text = m_emojier.get_title_text();
++            show_wayland_lookup_table(text);
++        } else {
++            // POPUP window takes the focus in Wayland.
++            if (m_is_wayland)
++                m_emojier.set_input_context_path(m_real_current_context_path);
++            m_emojier.show_all();
++        }
++    }
++
++
++    private bool is_emoji_lookup_table() {
++        if (m_is_wayland)
++            return is_visible_wayland_lookup_table();
++        else
++            return m_emojier.get_visible();
++    }
++
++
++    private void show_preedit_and_candidate(bool show_candidate) {
++        uint cursor_pos = 0;
++        if (!show_candidate)
++            cursor_pos = m_preedit.get_engine_preedit_cursor_pos();
++        update_preedit_text_received(
++                m_preedit.get_engine_preedit_text(),
++                cursor_pos,
++                true);
++        if (!show_candidate) {
++            hide_emoji_lookup_table();
++            return;
++        }
++        if (m_emojier == null)
++            return;
++        /* Wayland gives the focus on Emojir which is a GTK popup window
++         * and move the focus fom the current input context to Emojier.
++         * This forwards the lookup table to gnome-shell's lookup table
++         * but it enables one dimension lookup table only.
++         */
++        show_emoji_lookup_table();
++    }
++
++
+     public override void focus_in(string input_context_path) {
+         m_current_context_path = input_context_path;
+ 
+@@ -299,48 +776,280 @@ class PanelBinding : IBus.PanelService {
+     }
+ 
+ 
+-    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");
++    public override void panel_extension_received(IBus.ExtensionEvent event) {
++        m_extension_name = event.get_name();
++        if (m_extension_name != "emoji" && m_extension_name != "unicode") {
++            string format = "The name %s is not implemented in PanelExtension";
++            warning (format.printf(m_extension_name));
++            m_extension_name = "";
+             return;
+         }
+-        if (xevent.get_purpose() != "emoji") {
+-            string format = "The purpose %s is not implemented in PanelExtension";
+-            warning (format.printf(xevent.get_purpose()));
++        m_enable_extension = event.is_enabled;
++        if (!m_enable_extension) {
++            hide_emoji_lookup_table();
++            return;
++        }
++        if (!m_loaded_emoji)
++            set_emoji_lang();
++        if (!m_loaded_unicode && m_loaded_emoji) {
++            IBusEmojier.load_unicode_dict();
++            m_loaded_unicode = true;
++        }
++        if (m_emojier == null) {
++            m_emojier = new IBusEmojier();
++            // For title handling in gnome-shell
++            m_application.add_window(m_emojier);
++            m_emojier.candidate_clicked.connect((i, b, s) => {
++                if (!m_is_wayland)
++                    candidate_clicked_lookup_table(i, b, s);
++            });
++        }
++        m_emojier.reset();
++        m_emojier.set_annotation("");
++        m_preedit.set_extension_name(m_extension_name);
++        m_preedit.reset();
++        update_preedit_text_received(
++                m_preedit.get_engine_preedit_text(),
++                m_preedit.get_engine_preedit_cursor_pos(),
++                true);
++        string params = event.get_params();
++        if (params == "category-list") {
++            key_press_space();
++            show_preedit_and_candidate(true);
++        }
++    }
++
++
++    public override void set_cursor_location(int x,
++                                             int y,
++                                             int width,
++                                             int height) {
++        if (m_emojier != null)
++            m_emojier.set_cursor_location(x, y, width, height);
++    }
++
++
++    public override void update_preedit_text(IBus.Text text,
++                                             uint      cursor_pos,
++                                             bool      visible) {
++        m_preedit.set_engine_preedit_text(text);
++        if (visible)
++            m_preedit.show_engine_preedit_text();
++        else
++            m_preedit.hide_engine_preedit_text();
++        m_preedit.set_engine_preedit_cursor_pos(cursor_pos);
++        update_preedit_text_received(m_preedit.get_engine_preedit_text(),
++                                     m_preedit.get_engine_preedit_cursor_pos(),
++                                     visible);
++    }
++
++
++    public override void show_preedit_text() {
++        m_preedit.show_engine_preedit_text();
++        show_preedit_and_candidate(false);
++    }
++
++
++    public override void hide_preedit_text() {
++        m_preedit.hide_engine_preedit_text();
++        show_preedit_and_candidate(false);
++    }
++
++
++    public override bool process_key_event(uint keyval,
++                                           uint keycode,
++                                           uint state) {
++        if ((state & IBus.ModifierType.RELEASE_MASK) != 0)
++            return false;
++        uint modifiers = state;
++        bool show_candidate = false;
++        switch(keyval) {
++        case Gdk.Key.Escape:
++            show_candidate = key_press_escape();
++            if (!m_preedit.is_shown())
++                return true;
++            break;
++        case Gdk.Key.Return:
++        case Gdk.Key.KP_Enter:
++            if (m_extension_name == "unicode")
++                key_press_space();
++            show_candidate = key_press_enter();
++            if (!m_preedit.is_shown()) {
++                hide_emoji_lookup_table();
++                return true;
++            }
++            break;
++        case Gdk.Key.BackSpace:
++            m_preedit.backspace();
++            string annotation = m_preedit.get_text();
++            if (annotation == "" && m_extension_name == "unicode") {
++                key_press_escape();
++                return true;
++            }
++            m_emojier.set_annotation(annotation);
++            break;
++        case Gdk.Key.space:
++        case Gdk.Key.KP_Space:
++            show_candidate = key_press_space();
++            if (m_extension_name == "unicode") {
++                key_press_enter();
++                return true;
++            }
++            break;
++        case Gdk.Key.Right:
++        case Gdk.Key.KP_Right:
++            /* one dimension in Wayland, two dimensions in X11 */
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Down,
++                                                           modifiers);
++            } else {
++                show_candidate = key_press_cursor_horizontal(Gdk.Key.Right,
++                                                             modifiers);
++            }
++            break;
++        case Gdk.Key.Left:
++        case Gdk.Key.KP_Left:
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Up,
++                                                           modifiers);
++            } else {
++                show_candidate = key_press_cursor_horizontal(Gdk.Key.Left,
++                                                             modifiers);
++            }
++            break;
++        case Gdk.Key.Down:
++        case Gdk.Key.KP_Down:
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_horizontal(Gdk.Key.Right,
++                                                             modifiers);
++            } else {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Down,
++                                                           modifiers);
++            }
++            break;
++        case Gdk.Key.Up:
++        case Gdk.Key.KP_Up:
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_horizontal(Gdk.Key.Left,
++                                                             modifiers);
++            } else {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Up,
++                                                           modifiers);
++            }
++            break;
++        case Gdk.Key.Page_Down:
++        case Gdk.Key.KP_Page_Down:
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Down,
++                                                           modifiers);
++            } else {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Down,
++                                                           modifiers);
++            }
++            break;
++        case Gdk.Key.Page_Up:
++        case Gdk.Key.KP_Page_Up:
++            if (m_is_wayland) {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Up,
++                                                           modifiers);
++            } else {
++                show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Up,
++                                                           modifiers);
++            }
++            break;
++        case Gdk.Key.Home:
++        case Gdk.Key.KP_Home:
++            show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers);
++            break;
++        case Gdk.Key.End:
++        case Gdk.Key.KP_End:
++            show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers);
++            break;
++        default:
++            if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
++                show_candidate = key_press_control_keyval(keyval, modifiers);
++                break;
++            }
++            unichar ch = IBus.keyval_to_unicode(keyval);
++            if (ch.iscntrl())
++                return true;
++            string str = ch.to_string();
++            m_preedit.append_text(str);
++            string annotation = m_preedit.get_text();
++            m_emojier.set_annotation(annotation);
++            m_preedit.set_emoji("");
++            show_candidate = is_emoji_lookup_table();
++            break;
++        }
++        show_preedit_and_candidate(show_candidate);
++        return true;
++    }
++
++    public override void commit_text_received(IBus.Text text) {
++        unowned string? str = text.text;
++        if (str == null)
++            return;
++        /* Do not call convert_preedit_text() because it depends on
++         * each IME whether process_key_event() receives Shift-space or not.
++         */
++        m_preedit.append_text(str);
++        m_preedit.set_emoji("");
++        string annotation = m_preedit.get_text();
++        m_emojier.set_annotation(annotation);
++        show_preedit_and_candidate(false);
++    }
++
++    public override void page_up_lookup_table() {
++        bool show_candidate = key_press_cursor_vertical(Gdk.Key.Up, 0);
++        show_preedit_and_candidate(show_candidate);
++    }
++
++    public override void page_down_lookup_table() {
++        bool show_candidate = key_press_cursor_vertical(Gdk.Key.Down, 0);
++        show_preedit_and_candidate(show_candidate);
++    }
++
++    public override void cursor_up_lookup_table() {
++        bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, 0);
++        show_preedit_and_candidate(show_candidate);
++    }
++
++    public override void cursor_down_lookup_table() {
++        bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, 0);
++        show_preedit_and_candidate(show_candidate);
++    }
++
++    public override void candidate_clicked_lookup_table(uint index,
++                                                        uint button,
++                                                        uint state) {
++        if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) {
++            m_enable_extension = false;
++            hide_emoji_lookup_table();
++            m_preedit.hide();
++            IBus.ExtensionEvent event = new IBus.ExtensionEvent(
++                    "name", m_extension_name,
++                    "is-enabled", false,
++                    "is-extension", true);
++            panel_extension(event);
+             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;
++        if (m_emojier == null)
++            return;
++        bool show_candidate = false;
++        uint ncandidates = m_emojier.get_number_of_candidates();
++        if (ncandidates > 0 && ncandidates >= index) {
++            m_emojier.set_cursor_pos(index);
++            show_candidate = m_emojier.has_variants(index);
++            m_preedit.set_emoji(m_emojier.get_current_candidate());
+         } else {
+-            warning ("Not supported type %d".printf(xevent.get_event_type()));
+             return;
+         }
+-        Gdk.Event event = new Gdk.Event(event_type);
+-        uint32 time = xevent.get_time();
+-        if (time == 0)
+-            time = Gtk.get_current_event_time();
+-        event.key.time = time;
+-        X.Window xid = xevent.get_window();
+-        Gdk.Display? display = Gdk.Display.get_default();
+-        Gdk.Window? window = null;
+-        if (window == null && xid != 0) {
+-            window = Gdk.X11.Window.lookup_for_display(
+-                    display as Gdk.X11.Display, xid);
+-        }
+-        if (window == null && xid != 0) {
+-            window = new Gdk.X11.Window.foreign_for_display(
+-                    display as Gdk.X11.Display, xid);
+-        }
+-        if (window == null) {
+-            window = Gdk.get_default_root_window();
+-            window.ref();
+-        }
+-        event.key.window = window;
+-        handle_emoji_typing(event);
++        if (!show_candidate) {
++            IBus.Text text = m_preedit.get_commit_text();
++            commit_text_update_favorites(text);
++            hide_emoji_lookup_table();
++            return;
++        }
++        show_preedit_and_candidate(show_candidate);
+     }
+ }
+-- 
+2.14.3
+
+From 7cef5bf572596361bc502e8fa917569676a80372 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Wed, 20 Jun 2018 19:01:59 +0900
+Subject: [PATCH] setup: Replace emoji font with Unicode font
+
+Now the font settings of emoji is configurable in the session base
+but not the application base and the current font setting on ibus-setup
+effects on Unicode characters.
+Also fixed the progress bar on Unicode candidate table.
+---
+ setup/setup.ui       |   4 +-
+ src/tests/runtest    |   2 +-
+ ui/gtk3/emojier.vala | 213 ++++++++++++++++++++++++++++-----------------------
+ 3 files changed, 120 insertions(+), 99 deletions(-)
+
+diff --git a/setup/setup.ui b/setup/setup.ui
+index f1beb1de..9d9d7ee9 100644
+--- a/setup/setup.ui
++++ b/setup/setup.ui
+@@ -1010,9 +1010,9 @@
+                           <object class="GtkLabel" id="label_emoji_font">
+                             <property name="visible">True</property>
+                             <property name="can_focus">False</property>
+-                            <property name="tooltip_text" translatable="yes">Set a font of emoji candidates on the emoji dialog</property>
++                            <property name="tooltip_text" translatable="yes">Set a font of Unicode candidates on the emoji dialog</property>
+                             <property name="halign">start</property>
+-                            <property name="label" translatable="yes">Emoji font:</property>
++                            <property name="label" translatable="yes">Unicode font:</property>
+                             <property name="justify">right</property>
+                           </object>
+                           <packing>
+diff --git a/src/tests/runtest b/src/tests/runtest
+index b6b845d6..5c163083 100755
+--- a/src/tests/runtest
++++ b/src/tests/runtest
+@@ -142,7 +142,7 @@ run_test_case()
+         --daemonize \
+         --cache=none \
+         --panel=disable \
+-        --panel-extension=disable \
++        --emoji-extension=disable \
+         --config=default \
+         --verbose;
+ 
+diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
+index cd98c9d7..7beb6f0a 100644
+--- a/ui/gtk3/emojier.vala
++++ b/ui/gtk3/emojier.vala
+@@ -253,6 +253,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     private static string m_current_lang_id;
+     private static string m_emoji_font_family;
+     private static int m_emoji_font_size;
++    private static bool m_emoji_font_changed = false;
+     private static string[] m_favorites;
+     private static string[] m_favorite_annotations;
+     private static int m_emoji_max_seq_len;
+@@ -348,88 +349,20 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         add_action(action);
+         if (m_current_lang_id == null)
+             m_current_lang_id = "en";
+-        if (m_emoji_font_family == null)
++        if (m_emoji_font_family == null) {
+             m_emoji_font_family = "Monospace";
+-        if (m_emoji_font_size == 0)
++            m_emoji_font_changed = true;
++        }
++        if (m_emoji_font_size == 0) {
+             m_emoji_font_size = 16;
++            m_emoji_font_changed = true;
++        }
+         if (m_favorites == null)
+             m_favorites = {};
+         if (m_favorite_annotations == null)
+             m_favorite_annotations = {};
+ 
+-        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;
+-        }
+-        // Set en locale because de_DE's decimal_point is ',' instead of '.'
+-        string? backup_locale =
+-            Intl.setlocale(LocaleCategory.NUMERIC, null).dup();
+-        if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) {
+-          if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) {
+-              if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) {
+-                  warning("You don't install either en_US.UTF-8 or C.UTF-8 " +
+-                          "or C locale");
+-              }
+-          }
+-        }
+-        m_rgba = new ThemedRGBA(this);
+-        uint bg_red = (uint)(m_rgba.normal_bg.red * 255);
+-        uint bg_green = (uint)(m_rgba.normal_bg.green * 255);
+-        uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255);
+-        double bg_alpha = m_rgba.normal_bg.alpha;
+-        string data =
+-                "#IBusEmojierWhiteLabel { background-color: " +
+-                        "rgba(%u, %u, %u, %lf); ".printf(
+-                        bg_red, bg_green, bg_blue, bg_alpha) +
+-                "font-family: %s; font-size: %dpt; ".printf(
+-                        m_emoji_font_family, m_emoji_font_size) +
+-                "border-width: 4px; border-radius: 3px; } ";
+-
+-        uint fg_red = (uint)(m_rgba.selected_fg.red * 255);
+-        uint fg_green = (uint)(m_rgba.selected_fg.green * 255);
+-        uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255);
+-        double fg_alpha = m_rgba.selected_fg.alpha;
+-        bg_red = (uint)(m_rgba.selected_bg.red * 255);
+-        bg_green = (uint)(m_rgba.selected_bg.green * 255);
+-        bg_blue = (uint)(m_rgba.selected_bg.blue * 255);
+-        bg_alpha = m_rgba.selected_bg.alpha;
+-        data += "#IBusEmojierSelectedLabel { color: " +
+-                        "rgba(%u, %u, %u, %lf); ".printf(
+-                        fg_red, fg_green, fg_blue, fg_alpha) +
+-                "font-family: %s; font-size: %dpt; ".printf(
+-                        m_emoji_font_family, m_emoji_font_size) +
+-                "background-color: " +
+-                        "rgba(%u, %u, %u, %lf); ".printf(
+-                        bg_red, bg_green, bg_blue, bg_alpha) +
+-                "border-width: 4px; border-radius: 3px; }";
+-        data += "#IBusEmojierGoldLabel { color: " +
+-                        "rgba(%u, %u, %u, %lf); ".printf(
+-                        fg_red, fg_green, fg_blue, fg_alpha) +
+-                "font-family: %s; font-size: %dpt; ".printf(
+-                        m_emoji_font_family, m_emoji_font_size) +
+-                "background-color: #b09c5f; " +
+-                "border-width: 4px; border-radius: 3px; }";
+-
+-        Gtk.CssProvider css_provider = new Gtk.CssProvider();
+-        try {
+-            css_provider.load_from_data(data, -1);
+-        } catch (GLib.Error e) {
+-            warning("Failed css_provider_from_data: %s", e.message);
+-            return;
+-        }
+-        if (backup_locale != null)
+-            Intl.setlocale(LocaleCategory.NUMERIC, backup_locale);
+-        else
+-            Intl.setlocale(LocaleCategory.NUMERIC, "");
+-
+-        Gtk.StyleContext.add_provider_for_screen(
+-                screen,
+-                css_provider,
+-                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++        set_css_data();
+ 
+         m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+         add(m_vbox);
+@@ -795,6 +728,84 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     }
+ 
+ 
++    private void set_css_data() {
++        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;
++        }
++        // Set en locale because de_DE's decimal_point is ',' instead of '.'
++        string? backup_locale =
++            Intl.setlocale(LocaleCategory.NUMERIC, null).dup();
++        if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) {
++          if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) {
++              if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) {
++                  warning("You don't install either en_US.UTF-8 or C.UTF-8 " +
++                          "or C locale");
++              }
++          }
++        }
++        if (m_rgba == null)
++            m_rgba = new ThemedRGBA(this);
++        uint bg_red = (uint)(m_rgba.normal_bg.red * 255);
++        uint bg_green = (uint)(m_rgba.normal_bg.green * 255);
++        uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255);
++        double bg_alpha = m_rgba.normal_bg.alpha;
++        string data =
++                "#IBusEmojierWhiteLabel { background-color: " +
++                        "rgba(%u, %u, %u, %lf); ".printf(
++                        bg_red, bg_green, bg_blue, bg_alpha) +
++                "font-family: %s; font-size: %dpt; ".printf(
++                        m_emoji_font_family, m_emoji_font_size) +
++                "border-width: 4px; border-radius: 3px; } ";
++
++        uint fg_red = (uint)(m_rgba.selected_fg.red * 255);
++        uint fg_green = (uint)(m_rgba.selected_fg.green * 255);
++        uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255);
++        double fg_alpha = m_rgba.selected_fg.alpha;
++        bg_red = (uint)(m_rgba.selected_bg.red * 255);
++        bg_green = (uint)(m_rgba.selected_bg.green * 255);
++        bg_blue = (uint)(m_rgba.selected_bg.blue * 255);
++        bg_alpha = m_rgba.selected_bg.alpha;
++        data += "#IBusEmojierSelectedLabel { color: " +
++                        "rgba(%u, %u, %u, %lf); ".printf(
++                        fg_red, fg_green, fg_blue, fg_alpha) +
++                "font-family: %s; font-size: %dpt; ".printf(
++                        m_emoji_font_family, m_emoji_font_size) +
++                "background-color: " +
++                        "rgba(%u, %u, %u, %lf); ".printf(
++                        bg_red, bg_green, bg_blue, bg_alpha) +
++                "border-width: 4px; border-radius: 3px; }";
++        data += "#IBusEmojierGoldLabel { color: " +
++                        "rgba(%u, %u, %u, %lf); ".printf(
++                        fg_red, fg_green, fg_blue, fg_alpha) +
++                "font-family: %s; font-size: %dpt; ".printf(
++                        m_emoji_font_family, m_emoji_font_size) +
++                "background-color: #b09c5f; " +
++                "border-width: 4px; border-radius: 3px; }";
++
++        Gtk.CssProvider css_provider = new Gtk.CssProvider();
++        try {
++            css_provider.load_from_data(data, -1);
++        } catch (GLib.Error e) {
++            warning("Failed css_provider_from_data: %s", e.message);
++            return;
++        }
++        if (backup_locale != null)
++            Intl.setlocale(LocaleCategory.NUMERIC, backup_locale);
++        else
++            Intl.setlocale(LocaleCategory.NUMERIC, "");
++
++        Gtk.StyleContext.add_provider_for_screen(
++                screen,
++                css_provider,
++                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++    }
++
++
+     private void set_fixed_size() {
+         resize(20, 1);
+     }
+@@ -1038,7 +1049,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+             m_lookup_table.append_candidate(text);
+         }
+         m_backward = block_name;
+-        m_annotation = m_lookup_table.get_candidate(0).text;
++        if (m_lookup_table.get_number_of_candidates() > 0)
++            m_annotation = m_lookup_table.get_candidate(0).text;
+     }
+ 
+ 
+@@ -1385,6 +1397,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+     private void show_candidate_panel() {
+         remove_all_children();
+         set_fixed_size();
++        if (m_emoji_font_changed) {
++            set_css_data();
++            m_emoji_font_changed = false;
++        }
+         uint page_size = m_lookup_table.get_page_size();
+         uint ncandidates = m_lookup_table.get_number_of_candidates();
+         uint cursor = m_lookup_table.get_cursor_pos();
+@@ -1488,32 +1504,33 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ 
+             m_candidates += label;
+         }
+-        if (n > 0) {
+-            m_candidate_panel_is_visible = true;
+-            if (!m_is_up_side_down) {
+-                show_arrow_buttons();
+-                if (backward_button != null) {
+-                    m_vbox.add(backward_button);
+-                    backward_button.show_all();
+-                }
++        m_candidate_panel_is_visible = true;
++        if (!m_is_up_side_down) {
++            show_arrow_buttons();
++            if (backward_button != null) {
++                m_vbox.add(backward_button);
++                backward_button.show_all();
++            }
++            if (n > 0) {
+                 m_vbox.add(grid);
+                 grid.show_all();
+                 show_description();
+-                if (!m_loaded_unicode)
+-                    show_unicode_progress_bar();
+             }
+-            if (m_is_up_side_down) {
+-                if (!m_loaded_unicode)
+-                    show_unicode_progress_bar();
++            if (!m_loaded_unicode)
++                show_unicode_progress_bar();
++        } else {
++            if (!m_loaded_unicode)
++                show_unicode_progress_bar();
++            if (n > 0) {
+                 show_description();
+                 m_vbox.add(grid);
+                 grid.show_all();
+-                if (backward_button != null) {
+-                    m_vbox.add(backward_button);
+-                    backward_button.show_all();
+-                }
+-                show_arrow_buttons();
+             }
++            if (backward_button != null) {
++                m_vbox.add(backward_button);
++                backward_button.show_all();
++            }
++            show_arrow_buttons();
+         }
+     }
+ 
+@@ -2618,11 +2635,15 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+         Pango.FontDescription font_desc =
+                 Pango.FontDescription.from_string(emoji_font);
+         string font_family = font_desc.get_family();
+-        if (font_family != null)
++        if (font_family != null) {
+             m_emoji_font_family = font_family;
++            m_emoji_font_changed = true;
++        }
+         int font_size = font_desc.get_size() / Pango.SCALE;
+-        if (font_size != 0)
++        if (font_size != 0) {
+             m_emoji_font_size = font_size;
++            m_emoji_font_changed = true;
++        }
+     }
+ 
+ 
+-- 
+2.14.3
+

diff --git a/ibus.spec b/ibus.spec
index 6d2759f..8ccc98e 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -20,8 +20,6 @@
 %global with_kde5 0
 %endif
 
-%global with_emoji_harfbuzz 1
-
 %global ibus_api_version 1.0
 
 # for bytecompile in %%{_datadir}/ibus/setup
@@ -41,7 +39,7 @@
 
 Name:           ibus
 Version:        1.5.18
-Release:        6%{?dist}
+Release:        7%{?dist}
 Summary:        Intelligent Input Bus for Linux OS
 License:        LGPLv2+
 Group:          System Environment/Libraries
@@ -53,12 +51,8 @@ Source2:        %{name}.conf.5
 # Upstreamed patches.
 # Patch0:         %%{name}-HEAD.patch
 Patch0:         %{name}-HEAD.patch
-%if %with_emoji_harfbuzz
-# Under testing self rendering until Pango, Fontconfig, Cairo are stable
-Patch1:         %{name}-xx-emoji-harfbuzz.patch
-%endif
 # Under testing #1349148 #1385349 #1350291 #1406699 #1432252
-Patch2:         %{name}-1385349-segv-bus-proxy.patch
+Patch1:         %{name}-1385349-segv-bus-proxy.patch
 
 BuildRequires:  gettext-devel
 BuildRequires:  libtool
@@ -82,6 +76,7 @@ BuildRequires:  vala-devel
 BuildRequires:  vala-tools
 # for AM_GCONF_SOURCE_2 in configure.ac
 BuildRequires:  GConf2-devel
+BuildRequires:  git
 BuildRequires:  intltool
 BuildRequires:  iso-codes-devel
 BuildRequires:  libnotify-devel
@@ -92,11 +87,6 @@ BuildRequires:  qt5-qtbase-devel
 BuildRequires:  cldr-emoji-annotation
 BuildRequires:  unicode-emoji
 BuildRequires:  unicode-ucd
-%if %with_emoji_harfbuzz
-BuildRequires:  cairo-devel
-BuildRequires:  fontconfig-devel
-BuildRequires:  harfbuzz-devel
-%endif
 
 Requires:       %{name}-libs%{?_isa}   = %{version}-%{release}
 Requires:       %{name}-gtk2%{?_isa}   = %{version}-%{release}
@@ -260,14 +250,8 @@ The ibus-devel-docs package contains developer documentation for IBus
 
 
 %prep
-%setup -q
-# %%patch0 -p1
-%patch0 -p1
+%autosetup -S git
 # cp client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c ||
-%if %with_emoji_harfbuzz
-%patch1 -p1 -z .hb
-%endif
-%patch2 -p1 -z .segv
 
 # prep test
 diff client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c
@@ -299,9 +283,6 @@ autoreconf -f -i -v
 %if ! %with_kde5
     --disable-appindicator \
 %endif
-%if %with_emoji_harfbuzz
-    --enable-harfbuzz-for-emoji \
-%endif
     --enable-introspection \
     %{nil}
 
@@ -452,6 +433,10 @@ dconf update || :
 %{_datadir}/gtk-doc/html/*
 
 %changelog
+* Wed Jun 20 2018 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.18-7
+- Moved input focus on Emojier to engines' preedit
+- Removed ibus-xx-emoji-harfbuzz.patch not to change session emoji font
+
 * Tue Jun 19 2018 Miro Hrončok <mhroncok@redhat.com> - 1.5.18-6
 - Rebuilt for Python 3.7
 

                 reply	other threads:[~2026-05-31  2:06 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178019321180.1.9681944304768863712.rpms-ibus-6784864abcdb@fedoraproject.org \
    --to=tfujiwar@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox