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