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 -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 +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 ++#include ++#include "ibus.h" ++#include ++#include ++#include ++ ++#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 ", ++ "https://github.com/ibus/ibus/wiki", ++ "", ++ "ibus"); ++ desc = ibus_engine_desc_new ( ++ "xkbtest:us::eng", ++ "XKB Test", ++ "XKB Test", ++ "en", ++ "GPL", ++ "Takao Fujiwara ", ++ "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 +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 +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 +- * Copyright(c) 2015-2017 Takao Fujiwara ++ * Copyright(c) 2015-2018 Takao Fujiwara + * + * 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 +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 +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 +- * Copyright(c) 2015-2017 Takao Fujiwara ++ * Copyright(c) 2015-2018 Takao Fujiwara + * + * 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 get_ibus_schemas() { ++ string[] ids = {}; ++ if (engine_id != null) { ++ ids = engine_id.split(","); ++ } ++ GLib.SList ibus_schemas = new GLib.SList(); ++ 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 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 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 +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 @@ + 0 + none + +- ++ + True + False +- 5 +- 2 + 12 + 6 + 6 +@@ -117,8 +115,8 @@ + Next input method: + + +- GTK_FILL +- GTK_FILL ++ 0 ++ 0 + + + +@@ -131,10 +129,8 @@ + Previous input method: + + ++ 0 + 1 +- 2 +- GTK_FILL +- GTK_FILL + + + +@@ -143,6 +139,7 @@ + True + False + 6 ++ True + + + True +@@ -174,8 +171,7 @@ + + + 1 +- 2 +- GTK_FILL ++ 0 + + + +@@ -184,6 +180,7 @@ + True + False + 6 ++ True + + + > True +@@ -217,10 +214,7 @@ + + + 1 +- 2 + 1 +- 2 +- GTK_FILL + + + +@@ -232,10 +226,8 @@ + Enable or disable: + + ++ 0 + 2 +- 3 +- GTK_FILL +- GTK_FILL + + + +@@ -246,10 +238,8 @@ + Enable: + + ++ 0 + 3 +- 4 +- GTK_FILL +- GTK_FILL + + + +@@ -258,6 +248,7 @@ + True + False + 6 ++ True + + + True +@@ -289,10 +280,7 @@ + + + 1 +- 2 + 3 +- 4 +- GTK_FILL + + + +@@ -303,10 +291,8 @@ + Disable: + + ++ 0 + 4 +- 5 +- GTK_FILL +- GTK_FILL + + + +@@ -315,6 +301,7 @@ + True + False + 6 ++ True + + + True +@@ -346,10 +333,7 @@ + + + 1 +- 2 + 4 +- 5 +- GTK_FILL + + + +@@ -376,11 +360,9 @@ + 0 + none + +- ++ + True + False +- 7 +- 2 + 12 + 6 + 6 +@@ -393,10 +375,11 @@ + start + Candidates orientation: + right ++ True + + +- GTK_FILL +- GTK_FILL ++ 0 ++ 0 + + + +@@ -404,6 +387,7 @@ + True + False + model_candidates_orientation ++ True + + + +@@ -413,8 +397,7 @@ + + + 1 +- 2 +- GTK_FILL ++ 0 + + + +@@ -425,12 +408,11 @@ + start + Show property panel: + right ++ True + + ++ 0 + 1 +- 2 +- GTK_FILL +- GTK_FILL + + + +@@ -440,12 +422,11 @@ + start + Language panel position: + right ++ True + + ++ 0 + 2 +- 3 +- GTK_FILL +- GTK_FILL + + + +@@ -453,6 +434,7 @@ + True + False + model_panel_show_mode ++ True + + + +@@ -462,10 +444,7 @@ + + + 1 +- 2 + 1 +- 2 +- GTK_FILL + + + +@@ -473,6 +452,7 @@ + False + True + model_panel_position ++ True + + + +@@ -482,10 +462,7 @@ + + + 1 +- 2 + 2 +- 3 +- GTK_FILL + + + +@@ -499,13 +476,12 @@ + False + start + True ++ True + + +- 2 ++ 0 + 3 +- 4 +- GTK_FILL +- GTK_FILL ++ 2 + + + +@@ -519,13 +495,12 @@ + False + start + True ++ True + + +- 2 ++ 0 + 4 +- 5 +- GTK_FILL +- GTK_FILL ++ 2 + + + +@@ -539,13 +514,12 @@ + False + start + True ++ True + + +- 2 ++ 0 + 5 +- 6 +- GTK_FILL +- GTK_FILL ++ 2 + + + +@@ -559,12 +533,11 @@ + True + start + True ++ True + + ++ 0 + 6 +- 7 +- GTK_FILL +- GTK_FILL + + + +@@ -573,13 +546,11 @@ + True + True + False ++ True + + + 1 +- 2 + 6 +- 7 +- GTK_FILL + + + +@@ -888,11 +859,9 @@ + 0 + none + +- ++ + True + False +- 5 +- 2 + 12 + 6 + 6 +@@ -906,8 +875,8 @@ + Emoji choice: + + +- GTK_FILL +- GTK_FILL ++ 0 ++ 0 + + + +@@ -916,6 +885,7 @@ + True + False + 6 ++ true + + + True +@@ -947,8 +917,7 @@ + + + 1 +- 2 +- GTK_FILL ++ 0 + + + +-- +2.14.3 + +From 5ee3f48049ecf128391da6448ae7e74786bd171b Mon Sep 17 00:00:00 2001 +From: fujiwarat +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 +-# Copyright (c) 2017 Takao Fujiwara ++# Copyright (c) 2017-2018 Takao Fujiwara + # 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 +- * Copyright (C) 2008-2013 Red Hat, Inc. ++ * Copyright (C) 2018 Takao Fujiwara ++ * 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 @@ + Custom font name for language panel + + ++ ++ /schemas/desktop/ibus/panel/emoji/unicode-hotkey ++ /desktop/ibus/panel/emoji/unicode-hotkey ++ ibus ++ list ++ string ++ [<Control><Shift>u] ++ ++ Unicode shortcut keys for gtk_accelerator_parse ++ The shortcut keys for turning Unicode typing on or off ++ ++ + + /schemas/desktop/ibus/panel/emoji/hotkey + /desktop/ibus/panel/emoji/hotkey +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 +-# Copyright (c) 2010-2017 Takao Fujiwara ++# Copyright (c) 2010-2018 Takao Fujiwara + # 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 @@ + + True + False +- The shortcut keys for showing emoji dialog ++ The shortcut keys to enable conversions of emoji annotations or Unicode names + start +- Emoji choice: ++ Emoji annotation: + + + 0 +@@ -920,6 +920,60 @@ + 0 + + ++ ++ ++ True ++ False ++ The shortcut keys to enable Unicode code point conversions ++ start ++ Unicode code point: ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ horizontal ++ True ++ False ++ 6 ++ true ++ ++ ++ True ++ True ++ False ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ ... ++ False ++ True ++ True ++ False ++ False ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 1 ++ ++ + + + +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[] = + " " + " " + " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " + /* FIXME signals */ + " " + " " +@@ -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[] = + " " + " " + " " ++ " " ++ " " ++ " " ++ " " ++ " " + " " + " " + " " +@@ -221,7 +229,16 @@ static const gchar introspection_xml[] = + " " + " " + " " +- " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " + " " + /* Signals */ + " " +@@ -247,7 +264,23 @@ static const gchar introspection_xml[] = + " " + " " + " " ++ " " ++ " " ++ " " + " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " ++ " " + " " + " " + ""; +@@ -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(). ++ * ++ * Argument @user_data is ignored in this function. ++ * ++ */ ++ 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. ++ * ++ * Argument @user_data is ignored in this function. ++ * ++ */ ++ 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 categories = +- m_category_to_emojis_dict.get_keys(); +- // FIXME: How to cast GLib.CompareFunc 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 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? ++ private static GLib.SList? + lookup_emojis_from_annotation(string annotation) { + GLib.SList? total_emojis = null; + unowned GLib.SList? 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? 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 categories = ++ m_category_to_emojis_dict.get_keys(); ++ // FIXME: How to cast GLib.CompareFunc 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? 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 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? 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? 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? 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 +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 @@ + + True + False +- Set a font of emoji candidates on the emoji dialog ++ Set a font of Unicode candidates on the emoji dialog + start +- Emoji font: ++ Unicode font: + right + + +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 @@ -298,9 +282,6 @@ autoreconf -f -i -v --enable-wayland \ %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 - 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 - 1.5.18-6 - Rebuilt for Python 3.7