Blob Blame History Raw
From c6439d74d5472c95de4d5c2cdc6487bfd508e3d8 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 15 Mar 2018 16:57:02 +0900
Subject: [PATCH] ui/gtk3: Add num pad Enter, Down, Up, Left, Right on Emojier

BUG=rhbz#1554813
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/337690043
---
 ui/gtk3/emojier.vala | 25 +++++++++++++++++--------
 1 file changed, 17 insertions(+), 8 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 8707e432..24029703 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -1918,6 +1918,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
                 return true;
             break;
         case Gdk.Key.Return:
+        case Gdk.Key.KP_Enter:
             key_press_enter();
             return true;
         case Gdk.Key.BackSpace:
@@ -1959,29 +1960,37 @@ public class IBusEmojier : Gtk.ApplicationWindow {
             }
             return true;
         case Gdk.Key.Right:
-            key_press_cursor_horizontal(keyval, modifiers);
+        case Gdk.Key.KP_Right:
+            key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
             return true;
         case Gdk.Key.Left:
-            key_press_cursor_horizontal(keyval, modifiers);
+        case Gdk.Key.KP_Left:
+            key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
             return true;
         case Gdk.Key.Down:
-            key_press_cursor_vertical(keyval, modifiers);
+        case Gdk.Key.KP_Down:
+            key_press_cursor_vertical(Gdk.Key.Down, modifiers);
             return true;
         case Gdk.Key.Up:
-            key_press_cursor_vertical(keyval, modifiers);
+        case Gdk.Key.KP_Up:
+            key_press_cursor_vertical(Gdk.Key.Up, modifiers);
             return true;
         case Gdk.Key.Page_Down:
-            key_press_cursor_vertical(keyval, modifiers);
+        case Gdk.Key.KP_Page_Down:
+            key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers);
             return true;
         case Gdk.Key.Page_Up:
-            key_press_cursor_vertical(keyval, modifiers);
+        case Gdk.Key.KP_Page_Up:
+            key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers);
             return true;
         case Gdk.Key.Home:
-            if (key_press_cursor_home_end(keyval, modifiers))
+        case Gdk.Key.KP_Home:
+            if (key_press_cursor_home_end(Gdk.Key.Home, modifiers))
                 return true;
             break;
         case Gdk.Key.End:
-            if (key_press_cursor_home_end(keyval, modifiers))
+        case Gdk.Key.KP_End:
+            if (key_press_cursor_home_end(Gdk.Key.End, modifiers))
                 return true;
             break;
         case Gdk.Key.Insert:
-- 
2.14.3

From b184861396279d903e62bf6aad271a2205a79832 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 30 Mar 2018 12:33:59 +0900
Subject: [PATCH] ui/gtk3: Sort Unicode candidates

BUG=rhbz#1554714
R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/339430043
---
 ui/gtk3/emojier.vala | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 24029703..0c0865f1 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -1144,9 +1144,11 @@ public class IBusEmojier : Gtk.ApplicationWindow {
     lookup_emojis_from_annotation(string annotation) {
         GLib.SList<string>? total_emojis = null;
         unowned GLib.SList<string>? sub_emojis = null;
+        unowned GLib.SList<unichar>? sub_exact_unicodes = null;
         unowned GLib.SList<unichar>? sub_unicodes = null;
         int length = annotation.length;
         if (m_has_partial_match && length >= m_partial_match_length) {
+            GLib.SList<string>? sorted_emojis = null;
             foreach (unowned string key in
                      m_annotation_to_emojis_dict.get_keys()) {
                 if (key.length < length)
@@ -1173,16 +1175,29 @@ public class IBusEmojier : Gtk.ApplicationWindow {
                 sub_emojis = m_annotation_to_emojis_dict.lookup(key);
                 foreach (unowned string emoji in sub_emojis) {
                     if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
-                        total_emojis.append(emoji);
+                        sorted_emojis.insert_sorted(emoji, GLib.strcmp);
                     }
                 }
             }
+            foreach (string emoji in sorted_emojis) {
+                if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
+                    total_emojis.append(emoji);
+                }
+            }
         } else {
             sub_emojis = m_annotation_to_emojis_dict.lookup(annotation);
             foreach (unowned string emoji in sub_emojis)
                 total_emojis.append(emoji);
         }
+        sub_exact_unicodes = m_name_to_unicodes_dict.lookup(annotation);
+        foreach (unichar code in sub_exact_unicodes) {
+            string ch = code.to_string();
+            if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
+                total_emojis.append(ch);
+            }
+        }
         if (length >= m_partial_match_length) {
+            GLib.SList<string>? sorted_unicodes = null;
             foreach (unowned string key in m_name_to_unicodes_dict.get_keys()) {
                 bool matched = false;
                 if (key.index_of(annotation) >= 0)
@@ -1192,11 +1207,16 @@ public class IBusEmojier : Gtk.ApplicationWindow {
                 sub_unicodes = m_name_to_unicodes_dict.lookup(key);
                 foreach (unichar code in sub_unicodes) {
                     string ch = code.to_string();
-                    if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
-                        total_emojis.append(ch);
+                    if (sorted_unicodes.find_custom(ch, GLib.strcmp) == null) {
+                        sorted_unicodes.insert_sorted(ch, GLib.strcmp);
                     }
                 }
             }
+            foreach (string ch in sorted_unicodes) {
+                if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
+                    total_emojis.append(ch);
+                }
+            }
         }
         return total_emojis;
     }
-- 
2.14.3

From 5788be80685f397c3db3bdf4e672d67cfb9b3433 Mon Sep 17 00:00:00 2001
From: Jeremy Bicha <jbicha@ubuntu.com>
Date: Fri, 30 Mar 2018 12:37:27 +0900
Subject: [PATCH] Fix ucd directory override

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

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

Patch from Jeremy Bicha <jbicha@ubuntu.com>.
---
 configure.ac | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 6c00803f..d19aa874 100644
--- a/configure.ac
+++ b/configure.ac
@@ -666,7 +666,7 @@ AC_ARG_WITH(ucd-dir,
     AS_HELP_STRING([--with-ucd-dir[=DIR]],
         [Set the directory of UCD (Unicode Character Database) files.
          (default: "/usr/share/unicode/ucd")]),
-    UCD_DIR=$with_emoji_annotation_dir,
+    UCD_DIR=$with_ucd_dir,
     UCD_DIR="/usr/share/unicode/ucd"
 )
 AC_SUBST(UCD_DIR)
-- 
2.14.3

From 75a6667b6ad8c8cb801cb160b7b04625334f9094 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 5 Apr 2018 16:54:41 +0900
Subject: [PATCH] src/tests: Fix ibus-compose for the latest GTK

---
 src/tests/ibus-compose.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
index eb7b9f19..aabb36ac 100644
--- a/src/tests/ibus-compose.c
+++ b/src/tests/ibus-compose.c
@@ -2,6 +2,10 @@
 #include "ibus.h"
 #include "ibuscomposetable.h"
 
+#define GREEN "\033[0;32m"
+#define RED   "\033[0;31m"
+#define NC    "\033[0m"
+
 IBusBus *m_bus;
 IBusComposeTable *m_compose_table;
 IBusEngine *m_engine;
@@ -172,7 +176,12 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
                          guint           nchars,
                          gpointer        data)
 {
+/* https://gitlab.gnome.org/GNOME/gtk/commit/9981f46e0b
+ * The latest GTK does not emit "inserted-text" when the text is "".
+ */
+#if !GTK_CHECK_VERSION (3, 22, 16)
     static int n_loop = 0;
+#endif
     static guint stride = 0;
     guint i;
     int seq;
@@ -182,16 +191,18 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
 
     g_assert (m_compose_table != NULL);
 
+#if !GTK_CHECK_VERSION (3, 22, 16)
     if (n_loop % 2 == 1) {
         n_loop = 0;
         return;
     }
+#endif
     i = stride + (m_compose_table->max_seq_len + 2) - 1;
     seq = (i + 1) / (m_compose_table->max_seq_len + 2);
     if (m_compose_table->data[i] == code) {
-        test = "OK";
+        test = GREEN "PASS" NC;
     } else {
-        test = "NG";
+        test = RED "FAIL" NC;
         m_retval = -1;
     }
     g_print ("%05d/%05d %s expected: %04X typed: %04X\n",
@@ -207,7 +218,9 @@ window_inserted_text_cb (GtkEntryBuffer *buffer,
     }
 
     stride += m_compose_table->max_seq_len + 2;
+#if !GTK_CHECK_VERSION (3, 22, 16)
     n_loop++;
+#endif
     gtk_entry_set_text (entry, "");
 }
 
-- 
2.14.3

From 28d0c1d4bc47beb38995d84cc4bb1d539c08a070 Mon Sep 17 00:00:00 2001
From: Olivier Tilloy <olivier.tilloy@canonical.com>
Date: Fri, 6 Apr 2018 16:02:11 +0900
Subject: [PATCH] src: Make the call to chmod in ibus_bus_init conditional

BUG=https://github.com/ibus/ibus/issues/1996
---
 src/ibusbus.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/ibusbus.c b/src/ibusbus.c
index 11659c41..98820e8a 100644
--- a/src/ibusbus.c
+++ b/src/ibusbus.c
@@ -557,7 +557,6 @@ ibus_bus_init (IBusBus *bus)
     path = g_path_get_dirname (ibus_get_socket_path ());
 
     g_mkdir_with_parents (path, 0700);
-    g_chmod (path, 0700);
 
     if (stat (path, &buf) == 0) {
         if (buf.st_uid != getuid ()) {
@@ -565,6 +564,9 @@ ibus_bus_init (IBusBus *bus)
                        path, ibus_get_user_name ());
             return;
         }
+        if (buf.st_mode != (S_IFDIR | S_IRWXU)) {
+            g_chmod (path, 0700);
+        }
     }
 
     g_free (path);
-- 
2.14.3

From 32f2f2bab149ad766674e7421f7044ebe98bb0b6 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 6 Apr 2018 20:24:08 +0900
Subject: [PATCH] tests: Added an automation testing on console

test-console.sh runs /usr/bin/ibus-daemon on console after install ibus.

Login as root
  --builddir /root/ibus/src/tests --srcdir /root/ibus/src/tests

Also added DISABLE_GUI_TESTS parameters for make check.
---
 bus/Makefile.am       |   1 +
 src/tests/Makefile.am |   5 +-
 src/tests/runtest     | 151 +++++++++++++++++++------------
 test/test-console.sh  | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 343 insertions(+), 56 deletions(-)
 create mode 100755 test/test-console.sh

diff --git a/bus/Makefile.am b/bus/Makefile.am
index 8bcc8e16..76166a0f 100644
--- a/bus/Makefile.am
+++ b/bus/Makefile.am
@@ -122,6 +122,7 @@ TESTS_ENVIRONMENT = \
     top_builddir=$(top_builddir) \
     top_srcdir=$(top_srcdir) \
     builddir=$(builddir) \
+    srcdir=$(srcdir) \
     $(NULL)
 
 LOG_COMPILER = $(top_srcdir)/src/tests/runtest
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 125be3fc..8bcac8f2 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -3,7 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2007-2015 Red Hat, Inc.
+# Copyright (c) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-2018 Red Hat, Inc.
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -66,6 +67,8 @@ TESTS_ENVIRONMENT = \
 	top_builddir=$(top_builddir) \
 	top_srcdir=$(top_srcdir) \
 	builddir=$(builddir) \
+	srcdir=$(srcdir) \
+	DISABLE_GUI_TESTS=$(DISABLE_GUI_TESTS) \
 	$(NULL)
 
 LOG_COMPILER = $(srcdir)/runtest
diff --git a/src/tests/runtest b/src/tests/runtest
index 0e43fee5..b3b2a1ce 100755
--- a/src/tests/runtest
+++ b/src/tests/runtest
@@ -1,5 +1,8 @@
 #!/bin/sh
 
+# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+# vim:set et sts=4:
+
 # Run a test case given by the first argument in a separate directory.
 # This script may also launch $top_builddir/bus/ibus-daemon for testing.
 
@@ -17,6 +20,8 @@
 : ${top_builddir:=../..}
 : ${top_srcdir:=../..}
 : ${builddir:=.}
+: ${srcdir:=.}
+: ${DISABLE_GUI_TESTS:=''}
 
 BUS_REQUIRED_TESTS="
 ibus-bus
@@ -29,50 +34,51 @@ ibus-engine-switch
 ibus-compose
 test-stress
 "
+retval=0
 
 # Portable replacement of basename.
 func_basename () {
-  case "$1" in
+    case "$1" in
     */*)
-      expr "$1" : '.*/\(.*\)'
-      ;;
+        expr "$1" : '.*/\(.*\)'
+        ;;
     *)
-      echo "$1"
-  esac
+        echo "$1"
+    esac
 }
 
 # Portable replacement of dirname.
 func_dirname () {
-  case "$1" in
+    case "$1" in
     */*)
-      expr "$1" : '\(.*\)/.*'
-      ;;
+        expr "$1" : '\(.*\)/.*'
+        ;;
     *)
-      echo .
-  esac
+        echo .
+    esac
 }
 
 # Kill ibus-daemon process and remove temporary files.
 func_cleanup () {
-  tstdir=$1
-  if test -f $tstdir/ibus-daemon.pid; then
-    . $tstdir/ibus-daemon.pid
-    kill $IBUS_DAEMON_PID &> /dev/null
-  fi
-  rm -fr $tstdir
+    tstdir=$1
+    if test -f $tstdir/ibus-daemon.pid; then
+        . $tstdir/ibus-daemon.pid
+        kill $IBUS_DAEMON_PID &> /dev/null
+    fi
+    rm -fr $tstdir
 }
 
 # Prepare component files necessary for testing, under components/.
 func_copy_component () {
-  file=$1
-  base=`func_basename $file`
-  libexecdir=`func_dirname $file`
-  # top_srcdir != top_builddir in make dist
-  libexecdir=`echo "$libexecdir" | sed -e "s|$top_srcdir|$top_builddir|"`
-  if test -f $file.in; then
-    mkdir -p components
-    sed "s|@libexecdir@|$libexecdir|g" < $file.in > components/$base
-  fi
+    file=$1
+    base=`func_basename $file`
+    libexecdir=`func_dirname $file`
+    # top_srcdir != top_builddir in make dist
+    libexecdir=`echo "$libexecdir" | sed -e "s|$top_srcdir|$top_builddir|"`
+    if test -f $file.in; then
+        mkdir -p components
+        sed "s|@libexecdir@|$libexecdir|g" < $file.in > components/$base
+    fi
 }
 
 trap 'func_cleanup $tstdir' 1 2 3 15
@@ -80,44 +86,79 @@ trap 'func_cleanup $tstdir' 1 2 3 15
 tst=$1; shift
 tstdir=tmp-`func_basename $tst`
 
-test -d $tstdir || mkdir $tstdir
-
-( cd $tstdir
-
-  need_bus=no
-  for t in $BUS_REQUIRED_TESTS; do
+for t in $DISABLE_GUI_TESTS; do
     if test $t = `func_basename $tst`; then
-      need_bus=yes
+        exit 77
     fi
-  done
-
-  if test $need_bus = yes; then
-    func_copy_component "../$top_srcdir/engine/simple.xml"
-    func_copy_component "../$top_srcdir/conf/memconf/memconf.xml"
+done
 
-    IBUS_COMPONENT_PATH=$PWD/components
-    export IBUS_COMPONENT_PATH
+test -d $tstdir || mkdir $tstdir
 
-    IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid
-    export IBUS_ADDRESS_FILE
+run_test_case()
+{
+    pushd $tstdir
+
+    need_bus=no
+    for t in $BUS_REQUIRED_TESTS; do
+        if test $t = `func_basename $tst`; then
+            need_bus=yes
+        fi
+    done
+
+    if test $need_bus = yes; then
+        func_copy_component "../$top_srcdir/engine/simple.xml"
+        func_copy_component "../$top_srcdir/conf/memconf/memconf.xml"
+
+        IBUS_COMPONENT_PATH=$PWD/components
+        export IBUS_COMPONENT_PATH
+
+        IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid
+        export IBUS_ADDRESS_FILE
+
+        # Start ibus-daemon.
+        ../$top_builddir/bus/ibus-daemon \
+        --daemonize \
+        --cache=none \
+        --panel=disable \
+        --panel-extension=disable \
+        --config=default \
+        --verbose;
+
+        # Wait until all necessary components are up.
+        sleep 1
+    fi
 
-    # Start ibus-daemon.
-    ../$top_builddir/bus/ibus-daemon \
-    --daemonize \
-    --cache=none \
-    --panel=disable \
-    --panel-extension=disable \
-    --config=default \
-    --verbose;
+    "../$tst" ${1+"$@"}
 
-    # Wait until all necessary components are up.
-    sleep 1
-  fi
+    retval=`expr $retval \| $?`
 
-  exec "../$tst" ${1+"$@"} )
+    $popd
 
-retval=$?
+    func_cleanup $tstdir
+}
 
-func_cleanup $tstdir
+envfile=$srcdir/`func_basename $tst`.env
+if test -f $envfile ; then
+    ENVS="`cat $envfile`"
+fi;
+if test x"$ENVS" = x ; then
+    run_test_case
+else
+    LANG_backup=$LANG
+    i=1
+    for e in $ENVS; do
+        first=`echo "$e" | cut -c1-1`
+        if test x"$first" = x"#" ; then
+            continue
+        fi
+        export $e
+        echo "Run `func_basename $tst` on $e"
+        echo "======================="
+        run_test_case
+        echo ""
+        i=`expr $i + 1`
+    done
+    export LANG=$LANG_backup
+fi
 
 exit $retval
diff --git a/test/test-console.sh b/test/test-console.sh
new file mode 100755
index 00000000..7199f7a7
--- /dev/null
+++ b/test/test-console.sh
@@ -0,0 +1,242 @@
+#!/bin/sh
+# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+# vim:set noet ts=4:
+#
+# ibus-anthy - The Anthy engine for IBus
+#
+# Copyright (c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This test runs /usr/bin/ibus-daemon after install ibus
+#
+# # init 3
+# Login as root
+# # /root/ibus/tests/test-console.sh --tests ibus-compose \
+#   --builddir /root/ibus/src/tests --srcdir /root/ibus/src/tests
+
+PROGNAME=`basename $0`
+VERSION=0.1
+DISPLAY=:99.0
+BUILDDIR="."
+SRCDIR="."
+TEST_LOG=test-suite.log
+HAVE_GRAPHICS=1
+DESKTOP_COMMAND="gnome-session"
+PID_XORG=0
+PID_GNOME_SESSION=0
+TESTS=""
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+usage()
+{
+    echo -e \
+"This test runs /usr/bin/ibus-daemon after install ibus\n"                     \
+"$PROGNAME [OPTIONS…]\n"                                                       \
+"\n"                                                                           \
+"OPTIONS:\n"                                                                   \
+"-h, --help                       This help\n"                                 \
+"-v, --version                    Show version\n"                              \
+"-b, --builddir=BUILDDIR          Set the BUILDDIR\n"                          \
+"-s, --srcdir=SOURCEDIR           Set the SOURCEDIR\n"                         \
+"-c, --no-graphics                Use Xvfb instead of Xorg\n"                  \
+"-d, --desktop=DESKTOP            Run DESTKTOP. The default is gnome-session\n" \
+"-t, --tests=\"TESTS...\"           Run TESTS programs which is separated by space\n" \
+""
+}
+
+parse_args()
+{
+    # This is GNU getopt. "sudo port getopt" in BSD?
+    ARGS=`getopt -o hvb:s:cd:t: --long help,version,builddir:,srcdir:,no-graphics,desktop:,tests:\
+        -- "$@"`;
+    eval set -- "$ARGS"
+    while [ 1 ] ; do
+        case "$1" in
+        -h | --help )        usage; exit 0;;
+        -v | --version )     echo -e "$VERSION"; exit 0;;
+        -b | --builddir )    BUILDDIR="$2"; shift 2;;
+        -s | --srcdir )      SRCDIR="$2"; shift 2;;
+        -c | --no-graphics ) HAVE_GRAPHICS=0; shift;;
+        -d | --desktop )     DESKTOP_COMMAND="$2"; shift 2;;
+        -t | --tests )       TESTS="$2"; shift 2;;
+        -- )                 shift; break;;
+        * )                  usage; exit 1;;
+        esac
+    done
+}
+
+init_desktop()
+{
+    if test x$FORCE_TEST != x ; then
+        RUN_ARGS="$RUN_ARGS --force"
+    fi
+
+    if test ! -f $HOME/.config/gnome-initial-setup-done ; then
+        if test ! -f /var/lib/AccountsService/users/$USER ; then
+            mkdir -p /var/lib/AccountsService/users
+            cat >> /var/lib/AccountsService/users/$USER << _EOF
+[User]
+Language=ja_JP.UTF-8
+XSession=gnome
+SystemAccount=false
+_EOF
+        fi
+        mkdir -p $HOME/.config
+        touch $HOME/.config/gnome-initial-setup-done
+    fi
+}
+
+run_dbus_daemon()
+{
+    a=`ps -ef | grep dbus-daemon | grep "\-\-system" | grep -v session | grep -v grep`
+    if test x"$a" = x ; then
+        eval `dbus-launch --sh-syntax`
+    fi
+    SUSER=`echo "$USER" | cut -c 1-7`
+    a=`ps -ef | grep dbus-daemon | grep "$SUSER" | grep -v gdm | grep session | grep -v grep`
+    if test x"$a" = x ; then
+        systemctl --user start dbus
+        export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$UID/bus
+    fi
+    systemctl --user status dbus | col -b
+    ps -ef | grep dbus-daemon | grep "$SUSER" | grep -v gdm | egrep 'session|system' | grep -v grep
+    systemctl --user show-environment | col -b
+}
+
+run_desktop()
+{
+    if test $HAVE_GRAPHICS -eq 1 ; then
+        /usr/libexec/Xorg.wrap -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xorg.log -config ./xorg.conf -configdir . $DISPLAY &
+    else
+        /usr/bin/Xvfb $DISPLAY -noreset +extension GLX +extension RANDR +extension RENDER -screen 0 1280x1024x24 &
+    fi
+    PID_XORG=$!
+    sleep 1
+    export DISPLAY=$DISPLAY
+    $DESKTOP_COMMAND &
+    PID_GNOME_SESSION=$!
+    sleep 30
+    if test "$DESKTOP_COMMAND" != "gnome-session" ; then
+        ibus-daemon --daemonize --verbose
+        sleep 1
+    fi
+}
+
+count_case_result()
+{
+    retval=$1
+    pass=$2
+    fail=$3
+
+    if test $retval -eq  0 ; then
+        pass=`expr $pass + 1`
+    else
+        fail=`expr $fail + 1`
+    fi
+    echo $pass $fail
+}
+
+echo_case_result()
+{
+    retval=$1
+    tst=$2
+    log=$3
+    subtst=${4:-''}
+
+    if test $retval -eq  0 ; then
+        echo -e "${GREEN}PASS${NC}: $tst $subtst"
+    else
+        echo -e "${RED}FAIL${NC}: $tst $subtst"
+        echo "FAIL: $tst $subtst" >> $TEST_LOG
+        echo "======================" >> $TEST_LOG
+        echo "" >> $TEST_LOG
+        cat "$log" >> $TEST_LOG
+        echo "" >> $TEST_LOG
+    fi
+}
+
+run_test_suite()
+{
+    cd `dirname $0`
+    pass=0
+    fail=0
+
+    if test -f $TEST_LOG ; then
+        rm $TEST_LOG
+    fi
+    for tst in $TESTS; do
+        ENVS=
+        if test -f $SRCDIR/${tst}.env ; then
+            ENVS="`cat $SRCDIR/${tst}.env`"
+        fi
+        if test x"$ENVS" = x ; then
+            $BUILDDIR/$tst >&${tst}.log
+            retval=$?
+            read pass fail << EOF
+            `count_case_result $retval $pass $fail`
+EOF
+            echo_case_result $retval $tst ${tst}.log
+        else
+            LANG_backup=$LANG
+            i=1
+            for e in $ENVS; do
+                first=`echo "$e" | cut -c1-1`
+                if test x"$first" = x"#" ; then
+                    continue
+                fi
+                export $e
+                $BUILDDIR/$tst >&${tst}.${i}.log
+                retval=$?
+                read pass fail << EOF
+                `count_case_result $retval $pass $fail`
+EOF
+                echo_case_result $retval $tst ${tst}.${i}.log $e
+                i=`expr $i + 1`
+            done
+            export LANG=$LANG_backup
+        fi
+    done
+    echo ""
+    echo -e "# ${GREEN}PASS${NC}: $pass"
+    echo -e "# ${RED}FAIL${NC}: $fail"
+    if test -f ${TEST_LOG} ; then
+        echo ""
+        echo -e "${RED}See ${TEST_LOG}$NC"
+    fi
+}
+
+finit()
+{
+    if test "$DESKTOP_COMMAND" != "gnome-session" ; then
+        ibus exit
+    fi
+    kill $PID_GNOME_SESSION $PID_XORG
+}
+
+main()
+{
+    parse_args $@
+    init_desktop
+    run_dbus_daemon
+    run_desktop
+    run_test_suite
+    finit
+}
+
+main $@
-- 
2.14.3

From 68e162a59c7943ee6207ff7d21f9a75d1e6f2f79 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 6 Apr 2018 20:35:50 +0900
Subject: [PATCH] src/tests: Fix a typo in runtest

---
 src/tests/runtest | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/tests/runtest b/src/tests/runtest
index b3b2a1ce..09026be0 100755
--- a/src/tests/runtest
+++ b/src/tests/runtest
@@ -92,10 +92,9 @@ for t in $DISABLE_GUI_TESTS; do
     fi
 done
 
-test -d $tstdir || mkdir $tstdir
-
 run_test_case()
 {
+    test -d $tstdir || mkdir $tstdir
     pushd $tstdir
 
     need_bus=no
@@ -132,7 +131,7 @@ run_test_case()
 
     retval=`expr $retval \| $?`
 
-    $popd
+    popd
 
     func_cleanup $tstdir
 }
-- 
2.14.3

From c360cbd830943a4bfb0ece9cc07b99a426dc2121 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 9 Apr 2018 11:57:09 +0900
Subject: [PATCH] src/tests: Add ibus-compose.env

---
 src/tests/ibus-compose.env | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 src/tests/ibus-compose.env

diff --git a/src/tests/ibus-compose.env b/src/tests/ibus-compose.env
new file mode 100644
index 00000000..734ab8fa
--- /dev/null
+++ b/src/tests/ibus-compose.env
@@ -0,0 +1,3 @@
+LANG=el_GR.UTF-8
+LANG=fi_FI.UTF-8
+LANG=pt_BR.UTF-8
-- 
2.14.3

From 68bd2695c4cc6a06cb8a55a55fed2054d29f0995 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 13 Apr 2018 16:31:29 +0900
Subject: [PATCH] src/tests: Fix a typo

---
 src/tests/Makefile.am | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 8bcac8f2..11ebb531 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -68,7 +68,7 @@ TESTS_ENVIRONMENT = \
 	top_srcdir=$(top_srcdir) \
 	builddir=$(builddir) \
 	srcdir=$(srcdir) \
-	DISABLE_GUI_TESTS=$(DISABLE_GUI_TESTS) \
+	DISABLE_GUI_TESTS="$(DISABLE_GUI_TESTS)" \
 	$(NULL)
 
 LOG_COMPILER = $(srcdir)/runtest
-- 
2.14.3

From 8d4c4738d07b6850e56ae74d46b7b13b7382f865 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 13 Apr 2018 17:33:50 +0900
Subject: [PATCH] configure: Add --disable-python2 option

---
 bindings/pygobject/Makefile.am |  6 ++++++
 configure.ac                   | 37 ++++++++++++++++++++++++++++---------
 2 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/bindings/pygobject/Makefile.am b/bindings/pygobject/Makefile.am
index 238a537a..fb2e2a7a 100644
--- a/bindings/pygobject/Makefile.am
+++ b/bindings/pygobject/Makefile.am
@@ -4,6 +4,8 @@
 #
 # Copyright (c) 2012 Daiki Ueno <ueno@unixuser.org>
 # Copyright (c) 2014-2016 Peng Huang <shawn.p.huang@gmail.com>
+# Copyright (c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2012-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
@@ -22,11 +24,13 @@
 
 NULL =
 
+if ENABLE_PYTHON2
 py2_compile = PYTHON=$(PYTHON2) $(SHELL) $(py_compile)
 overrides2dir = $(py2overridesdir)
 overrides2_DATA =				\
 	gi/overrides/IBus.py			\
 	$(NULL)
+endif
 
 overridesdir = $(pyoverridesdir)
 overrides_PYTHON =				\
@@ -56,6 +60,7 @@ EXTRA_DIST =					\
 	$(NULL)
 
 install-data-hook:
+if ENABLE_PYTHON2
 	@for data in $(overrides2_DATA); do \
 	    file=`echo $$data | sed -e 's|^.*/||'`; \
 	    dlist="$$dlist $$file"; \
@@ -63,6 +68,7 @@ install-data-hook:
 	$(py2_compile) --destdir "$(DESTDIR)" \
 	               --basedir "$(overrides2dir)" \
 	               $$dlist
+endif
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/configure.ac b/configure.ac
index d19aa874..085cecb8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -391,6 +391,14 @@ fi
 AC_PATH_PROG(ENV_IBUS_TEST, env)
 AC_SUBST(ENV_IBUS_TEST)
 
+AC_ARG_ENABLE(python2,
+    AS_HELP_STRING([--disable-python2],
+                   [Do not install bindings/pygobject/gi and ibus for python2.
+                    '--disable-python2' bring '--disable-python-library'.]),
+    [enable_python2=$enableval],
+    [enable_python2=yes]
+)
+
 AC_ARG_ENABLE(python-library,
     AS_HELP_STRING([--enable-python-library],
                    [Use ibus python library]),
@@ -405,10 +413,6 @@ AC_ARG_ENABLE(setup,
     [enable_setup=yes]
 )
 
-AM_CONDITIONAL([ENABLE_PYTHON_LIBRARY], [test x"$enable_python_library" = x"yes"])
-AM_CONDITIONAL([ENABLE_SETUP], [test x"$enable_setup" = x"yes"])
-AM_CONDITIONAL([ENABLE_DAEMON], [true])
-
 # Define python version
 AC_ARG_WITH(python,
     AS_HELP_STRING([--with-python[=PATH]],
@@ -417,12 +421,24 @@ AC_ARG_WITH(python,
 )
 
 AM_PATH_PYTHON([2.5])
-AC_PATH_PROG(PYTHON2, python2)
 
-if test x"$PYTHON2" = x""; then
-    PYTHON2=$PYTHON
+if test x"$enable_python2" != x"yes"; then
+    enable_python_library=no
+    PYTHON2=
+    enable_python2="no (disabled, use --enable-python2 to enable)"
+else
+    AC_PATH_PROG(PYTHON2, python2)
+
+    if test x"$PYTHON2" = x""; then
+        PYTHON2=$PYTHON
+    fi
 fi
 
+AM_CONDITIONAL([ENABLE_PYTHON2], [test x"$enable_python2" = x"yes"])
+AM_CONDITIONAL([ENABLE_PYTHON_LIBRARY], [test x"$enable_python_library" = x"yes"])
+AM_CONDITIONAL([ENABLE_SETUP], [test x"$enable_setup" = x"yes"])
+AM_CONDITIONAL([ENABLE_DAEMON], [true])
+
 PYGOBJECT_REQUIRED=3.0.0
 
 PKG_CHECK_EXISTS([pygobject-3.0 >= $PYGOBJECT_REQUIRED],
@@ -434,8 +450,10 @@ if test "x$enable_pygobject" = "xyes"; then
     pyoverridesdir=`$PYTHON -c "import gi; print(gi._overridesdir)"`
     AC_SUBST(pyoverridesdir)
 
-    py2overridesdir=`$PYTHON2 -c "import gi; print(gi._overridesdir)"`
-    AC_SUBST(py2overridesdir)
+    if test x"$enable_python2" = x"yes"; then
+        py2overridesdir=`$PYTHON2 -c "import gi; print(gi._overridesdir)"`
+        AC_SUBST(py2overridesdir)
+    fi
 fi
 
 AM_CONDITIONAL(ENABLE_PYGOBJECT, test x"$enable_pygobject" = "xyes")
@@ -752,6 +770,7 @@ Build options:
   CFLAGS                        $CFLAGS
   PYTHON                        $PYTHON
   PYTHON2                       $PYTHON2
+  Enable python2                $enable_python2
   Gtk2 immodule dir             $GTK2_IM_MODULEDIR
   Gtk3 immodule dir             $GTK3_IM_MODULEDIR
   Build gtk2 immodule           $enable_gtk2
-- 
2.14.3

From 7bc160f2139799b853678264c6b01277f0721336 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 13 Apr 2018 19:39:09 +0900
Subject: [PATCH] bus: Add DISABLE_GUI_TESTS for test-stress

---
 bus/Makefile.am | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bus/Makefile.am b/bus/Makefile.am
index 76166a0f..dda79eac 100644
--- a/bus/Makefile.am
+++ b/bus/Makefile.am
@@ -110,7 +110,6 @@ if ENABLE_EMOJI_DICT
 AM_CFLAGS += -DEMOJI_DICT
 endif
 
-
 if ENABLE_TESTS
 TESTS = \
 	test-matchrule \
@@ -123,6 +122,7 @@ TESTS_ENVIRONMENT = \
     top_srcdir=$(top_srcdir) \
     builddir=$(builddir) \
     srcdir=$(srcdir) \
+    DISABLE_GUI_TESTS="$(DISABLE_GUI_TESTS)" \
     $(NULL)
 
 LOG_COMPILER = $(top_srcdir)/src/tests/runtest
-- 
2.14.3

From 10cc30eac200d10b581d9d2122d5a732f4880943 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 17 Apr 2018 14:00:20 +0900
Subject: [PATCH] src/tests: Enable GSettings in runtest

---
 src/tests/runtest | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/src/tests/runtest b/src/tests/runtest
index 09026be0..35825b1b 100755
--- a/src/tests/runtest
+++ b/src/tests/runtest
@@ -34,6 +34,8 @@ ibus-engine-switch
 ibus-compose
 test-stress
 "
+IBUS_SCHEMA_FILE='org.freedesktop.ibus.gschema.xml'
+
 retval=0
 
 # Portable replacement of basename.
@@ -92,6 +94,12 @@ for t in $DISABLE_GUI_TESTS; do
     fi
 done
 
+# IBusEngine has GSettings
+if test ! -f "$top_builddir/data/dconf/$IBUS_SCHEMA_FILE" ; then
+    echo "NOT FOUND $top_builddir/data/dconf/$IBUS_SCHEMA_FILE"
+    exit -1
+fi
+
 run_test_case()
 {
     test -d $tstdir || mkdir $tstdir
@@ -114,6 +122,20 @@ run_test_case()
         IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid
         export IBUS_ADDRESS_FILE
 
+        cp "../$top_builddir/data/dconf/$IBUS_SCHEMA_FILE" $PWD
+        glib-compile-schemas $PWD
+        if test $? -ne 0 ; then
+            echo "FAILED glib-compile-schemas"
+            retval=1
+            return
+        fi
+        if test ! -f $PWD/gschemas.compiled ; then
+            echo "NOT FOUND $PWD/gschemas.compiled"
+            retval=1
+            return
+        fi
+        export GSETTINGS_SCHEMA_DIR=$PWD
+
         # Start ibus-daemon.
         ../$top_builddir/bus/ibus-daemon \
         --daemonize \
-- 
2.14.3

From 3280848b42b07afbac3d59066474c5f429de9182 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 17 Apr 2018 14:43:02 +0900
Subject: [PATCH] bus: Enable sub package of gtkextension.xml and
 ibus-extension-gtk3

GNOME destkop asked not to install ibus-extension-gtk3 by default
since the UI is not called by gnome-shell.

BUG=rhbz#1567689
---
 bus/main.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bus/main.c b/bus/main.c
index 7aa89fc4..e1cc423b 100644
--- a/bus/main.c
+++ b/bus/main.c
@@ -293,7 +293,7 @@ main (gint argc, gchar **argv)
         if (component) {
             bus_component_set_restart (component, restart);
         }
-        if (component == NULL ||
+        if (component != NULL &&
             !bus_component_start (component, g_verbose)) {
             g_printerr ("Can not execute default panel program\n");
             exit (-1);
-- 
2.14.3

From d8f901f856ddd75baba5826038d1346c5a43d048 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 20 Apr 2018 15:58:06 +0900
Subject: [PATCH] Replace OnlyShowIn= with NoDisplay=true

BUG=rhbz#1567689
---
 ui/gtk3/ibus-extension-gtk3.desktop.in.in | 2 +-
 ui/gtk3/ibus-ui-emojier.desktop.in.in     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/ui/gtk3/ibus-extension-gtk3.desktop.in.in b/ui/gtk3/ibus-extension-gtk3.desktop.in.in
index 6ec5585f..a119ec8e 100644
--- a/ui/gtk3/ibus-extension-gtk3.desktop.in.in
+++ b/ui/gtk3/ibus-extension-gtk3.desktop.in.in
@@ -3,4 +3,4 @@ _Name=Emoji Choice
 Icon=ibus
 Exec=@libexecdir@/ibus-extension-gtk3
 Type=Application
-OnlyShowIn=
+NoDisplay=true
diff --git a/ui/gtk3/ibus-ui-emojier.desktop.in.in b/ui/gtk3/ibus-ui-emojier.desktop.in.in
index f4b750a8..6d9422d5 100644
--- a/ui/gtk3/ibus-ui-emojier.desktop.in.in
+++ b/ui/gtk3/ibus-ui-emojier.desktop.in.in
@@ -3,4 +3,4 @@ _Name=Emoji Choice
 Icon=ibus
 Exec=ibus emoji
 Type=Application
-OnlyShowIn=
+NoDisplay=true
-- 
2.14.3

From 886ad3651d16dd821e2526e8601c69738533a7e8 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 7 May 2018 12:35:03 +0900
Subject: [PATCH] src: Fix SEGV in IBusEngine if no emoji shortcut keys

BUG=https://github.com/ibus/ibus/issues/2005
---
 src/ibusengine.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/ibusengine.c b/src/ibusengine.c
index 9a0b1a8a..fd61102a 100644
--- a/src/ibusengine.c
+++ b/src/ibusengine.c
@@ -925,6 +925,9 @@ 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) {
-- 
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