public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ibus] autotool: Added panel extension for emoji keybinding not to depen on desktops
@ 2026-05-31 2:06 Takao Fujiwara
0 siblings, 0 replies; only message in thread
From: Takao Fujiwara @ 2026-05-31 2:06 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/ibus
Branch : autotool
Commit : 98919f74e387656fe5f8a4ba5eb8d59e612f246b
Author : Takao Fujiwara <tfujiwar@redhat.com>
Date : 2018-02-21T16:07:48+09:00
Stats : +4242/-51 in 6 file(s)
URL : https://src.fedoraproject.org/rpms/ibus/c/98919f74e387656fe5f8a4ba5eb8d59e612f246b?branch=autotool
Log:
Added panel extension for emoji keybinding not to depen on desktops
---
diff --git a/.gitignore b/.gitignore
index cb853da..627f1d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@
/ibus-po-1.5.9-20141001.tar.gz
/ibus-po-1.5.10-20150402.tar.gz
/ibus-po-1.5.14-20160909.tar.gz
+/ibus-po-1.5.17-20180221.tar.gz
ibus-1.3.6.tar.gz
/ibus-1.3.7.tar.gz
/ibus-1.3.8.tar.gz
diff --git a/ibus-1385349-segv-bus-proxy.patch b/ibus-1385349-segv-bus-proxy.patch
index a9c1a73..02912a5 100644
--- a/ibus-1385349-segv-bus-proxy.patch
+++ b/ibus-1385349-segv-bus-proxy.patch
@@ -1,6 +1,6 @@
-From 8ea0d3f25078c612b4b16c955c1c0c17e764d8c5 Mon Sep 17 00:00:00 2001
+From 4ad2f160e2af0b71148b3f7726e71f26a107ff1c Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 27 Jul 2017 18:56:01 +0900
+Date: Wed, 21 Feb 2018 15:05:18 +0900
Subject: [PATCH] bus: Fix SEGV in bus_panel_proxy_focus_in()
BUG=rhbz#1349148
@@ -10,11 +10,11 @@ BUG=rhbz#1406699
BUG=rhbz#1432252
---
bus/dbusimpl.c | 38 ++++++++++++++++++++++++++++++++------
- bus/ibusimpl.c | 22 +++++++++++++++++++---
- 2 files changed, 51 insertions(+), 9 deletions(-)
+ bus/ibusimpl.c | 21 ++++++++++++++++++---
+ 2 files changed, 50 insertions(+), 9 deletions(-)
diff --git a/bus/dbusimpl.c b/bus/dbusimpl.c
-index b54ef81..e4dd868 100644
+index b54ef817..e4dd8683 100644
--- a/bus/dbusimpl.c
+++ b/bus/dbusimpl.c
@@ -2,7 +2,8 @@
@@ -124,41 +124,37 @@ index b54ef81..e4dd868 100644
if (incoming) {
/* is incoming message */
diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
-index f99307a..2d43ff6 100644
+index 58d205cf..34f6c909 100644
--- a/bus/ibusimpl.c
+++ b/bus/ibusimpl.c
-@@ -2,7 +2,8 @@
- /* vim:set et sts=4: */
- /* ibus - The Input Bus
- * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright (C) 2008-2013 Red Hat, Inc.
-+ * Copyright (C) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2017 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
-@@ -323,11 +324,14 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
- g_assert (new_name != NULL);
- g_assert (BUS_IS_IBUS_IMPL (ibus));
+@@ -357,13 +357,16 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
+ else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION))
+ panel_type = PANEL_TYPE_EXTENSION;
-- if (g_strcmp0 (name, IBUS_SERVICE_PANEL) == 0) {
+- if (panel_type != PANEL_TYPE_NONE) {
+ do {
-+ if (g_strcmp0 (name, IBUS_SERVICE_PANEL) != 0)
++ if (panel_type == PANEL_TYPE_NONE)
+ break;
if (g_strcmp0 (new_name, "") != 0) {
/* a Panel process is started. */
BusConnection *connection;
BusInputContext *context = NULL;
+ BusPanelProxy **panel = (panel_type == PANEL_TYPE_PANEL) ?
+ &ibus->panel : &ibus->extension;
+ GDBusConnection *dbus_connection = NULL;
- if (ibus->panel != NULL) {
- ibus_proxy_destroy ((IBusProxy *) ibus->panel);
-@@ -338,6 +342,18 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
- connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
+ if (*panel != NULL) {
+ ibus_proxy_destroy ((IBusProxy *)(*panel));
+@@ -372,9 +375,21 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
+ g_assert (*panel == NULL);
+ }
+
+- connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
++ connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS,
++ new_name);
g_return_if_fail (connection != NULL);
+ dbus_connection = bus_connection_get_dbus_connection (connection);
-+
+ /* rhbz#1349148 rhbz#1385349
+ * Avoid SEGV of BUS_IS_PANEL_PROXY (ibus->panel)
+ * This function is called during destroying the connection
@@ -169,10 +165,10 @@ index f99307a..2d43ff6 100644
+ break;
+ }
+
- ibus->panel = bus_panel_proxy_new (connection);
+ *panel = bus_panel_proxy_new (connection, panel_type);
- g_signal_connect (ibus->panel,
-@@ -366,7 +382,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
+ g_signal_connect (*panel,
+@@ -406,7 +421,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
}
}
}
@@ -182,5 +178,5 @@ index f99307a..2d43ff6 100644
bus_ibus_impl_component_name_owner_changed (ibus, name, old_name, new_name);
}
--
-2.9.3
+2.14.3
diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index 6b21ef5..8d1fa9f 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -4442,3 +4442,4184 @@ index 555ea68f..0bf34da8 100644
--
2.14.3
+From fb07f64764f18f702221ff5574b2fd2193f051f0 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Tue, 20 Feb 2018 17:25:07 +0900
+Subject: [PATCH] Implement ibus-extension-gtk3 for the global keybinding
+
+Currently IBus panel (ibus-ui-gtk3) is not available in GNOME and Plasma
+so the emoji and unicode point typings are not available in GNOME and Plasma.
+The workaround `ibus emoji` command is available but it put the selected
+character into the copy buffer and users have to paste the character.
+
+Originaly the emoji feature was implemented in IBus GtkIMModule but
+it had several problems; the first is the keybinding is hard-coded
+and IBus GtkIMModule does not use GSettings for the customized settings.
+The second is the feature was available for GTK applications.
+The third is that XKB input sources uses gtk-im-context-simple
+but not ibus in GNOME desktop so users have to add an IM input sources
+to enable IBus for the XKB input sources. The fourth is the feature
+was available for IBusEngineSimple only and other IBus IMEs need to
+inherit that class to get the emoji feature. The fifth is that
+emoji typing is available for English only since IBusEngineSimple
+had the feature. The sixth is that the default one dimension lookup
+window was not useful to choose an emoji and needed two dimensions
+lookup window.
+
+And the implementation was moved from IBus GtkIMModule to IBus panel
+to fix above problems.
+But users have to use `ibus emoji` at present if ibus-ui-gtk3
+is not available.
+
+Now I think to move the emoji feature from ibus-ui-gtk3 to another
+IBus component; ibus-extension-gtk3 which manages the Ctrl-Shift-e.
+GNOME and Plasma desktops still do not show the GUI menu but
+the shortcut key is available in this implementation.
+
+BUG=RHBZ#1430501
+R=Shawn.P.Huang@gmail.com
+
+Review URL: https://codereview.appspot.com/339300043
+---
+ bindings/vala/IBus-1.0-custom.vala | 4 +
+ bus/Makefile.am | 7 +-
+ bus/ibusimpl.c | 82 ++-
+ bus/main.c | 25 +-
+ bus/marshalers.list | 1 +
+ bus/panelproxy.c | 57 +-
+ bus/panelproxy.h | 20 +-
+ src/Makefile.am | 4 +-
+ src/ibus.h | 1 +
+ src/ibusmarshalers.list | 1 +
+ src/ibuspanelservice.c | 81 ++-
+ src/ibuspanelservice.h | 15 +-
+ src/ibusserializable.c | 7 +-
+ src/ibusserializable.h | 18 +-
+ src/ibusshare.c | 4 +-
+ src/ibusshare.h | 17 +-
+ src/ibusxevent.c | 1004 ++++++++++++++++++++++++++++++++++++
+ src/ibusxevent.h | 294 +++++++++++
+ src/tests/runtest | 1 +
+ ui/gtk3/Makefile.am | 59 ++-
+ ui/gtk3/bindingcommon.vala | 215 ++++++++
+ ui/gtk3/candidatearea.vala | 102 ----
+ ui/gtk3/extension.vala | 124 +++++
+ ui/gtk3/gtkextension.xml.in | 12 +
+ ui/gtk3/iconwidget.vala | 103 ++++
+ ui/gtk3/panel.vala | 408 +++------------
+ ui/gtk3/panelbinding.vala | 335 ++++++++++++
+ 27 files changed, 2506 insertions(+), 495 deletions(-)
+ create mode 100644 src/ibusxevent.c
+ create mode 100644 src/ibusxevent.h
+ create mode 100644 ui/gtk3/bindingcommon.vala
+ create mode 100644 ui/gtk3/extension.vala
+ create mode 100644 ui/gtk3/gtkextension.xml.in
+ create mode 100644 ui/gtk3/panelbinding.vala
+
+diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala
+index 144d75e2..cf1fc3fa 100644
+--- a/bindings/vala/IBus-1.0-custom.vala
++++ b/bindings/vala/IBus-1.0-custom.vala
+@@ -6,4 +6,8 @@ namespace IBus {
+ [CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)]
+ public Text.from_static_string (string str);
+ }
++ public class XEvent : IBus.Serializable {
++ [CCode (cname = "ibus_x_event_new", has_construct_function = true)]
++ public XEvent (string first_property_name, ...);
++ }
+ }
+diff --git a/bus/Makefile.am b/bus/Makefile.am
+index 864ba923..8bcc8e16 100644
+--- a/bus/Makefile.am
++++ b/bus/Makefile.am
+@@ -3,7 +3,8 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2013 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2007-2013 Red Hat, Inc.
++# Copyright (c) 2013-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2007-2018 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+ # modify it under the terms of the GNU Lesser General Public
+@@ -105,6 +106,10 @@ marshalers.c: marshalers.h marshalers.list
+ $(GLIB_GENMARSHAL) --prefix=bus_marshal $(srcdir)/marshalers.list --body --internal) > $@.tmp && \
+ mv $@.tmp $@
+
++if ENABLE_EMOJI_DICT
++AM_CFLAGS += -DEMOJI_DICT
++endif
++
+
+ if ENABLE_TESTS
+ TESTS = \
+diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
+index f99307ad..58d205cf 100644
+--- a/bus/ibusimpl.c
++++ b/bus/ibusimpl.c
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2013 Red Hat, Inc.
++ * Copyright (C) 2011-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -73,6 +74,7 @@ struct _BusIBusImpl {
+
+ BusInputContext *focused_context;
+ BusPanelProxy *panel;
++ BusPanelProxy *extension;
+
+ /* a default keymap of ibus-daemon (usually "us") which is used only
+ * when use_sys_layout is FALSE. */
+@@ -290,12 +292,37 @@ _panel_destroy_cb (BusPanelProxy *panel,
+ g_assert (BUS_IS_PANEL_PROXY (panel));
+ g_assert (BUS_IS_IBUS_IMPL (ibus));
+
+- g_return_if_fail (ibus->panel == panel);
+-
+- ibus->panel = NULL;
++ if (ibus->panel == panel)
++ ibus->panel = NULL;
++ else if (ibus->extension == panel)
++ ibus->extension = NULL;
++ else
++ g_return_if_reached ();
+ g_object_unref (panel);
+ }
+
++static void
++_panel_panel_extension_cb (BusPanelProxy *panel,
++ GVariant *parameters,
++ BusIBusImpl *ibus)
++{
++ if (!ibus->extension) {
++ g_warning ("Panel extension is not running.");
++ return;
++ }
++
++ g_return_if_fail (BUS_IS_IBUS_IMPL (ibus));
++ g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->extension));
++
++ /* Use the DBus method because it seems any DBus signal,
++ * g_dbus_message_new_signal(), cannot be reached to the server. */
++ g_dbus_proxy_call (G_DBUS_PROXY (ibus->extension),
++ "PanelExtensionReceived",
++ parameters,
++ G_DBUS_CALL_FLAGS_NONE,
++ -1, NULL, NULL, NULL);
++}
++
+ static void
+ _registry_changed_cb (IBusRegistry *registry,
+ BusIBusImpl *ibus)
+@@ -317,33 +344,47 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
+ const gchar *new_name,
+ BusIBusImpl *ibus)
+ {
++ PanelType panel_type = PANEL_TYPE_NONE;
++
+ g_assert (BUS_IS_DBUS_IMPL (dbus));
+ g_assert (name != NULL);
+ g_assert (old_name != NULL);
+ g_assert (new_name != NULL);
+ g_assert (BUS_IS_IBUS_IMPL (ibus));
+
+- if (g_strcmp0 (name, IBUS_SERVICE_PANEL) == 0) {
++ if (!g_strcmp0 (name, IBUS_SERVICE_PANEL))
++ panel_type = PANEL_TYPE_PANEL;
++ else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION))
++ panel_type = PANEL_TYPE_EXTENSION;
++
++ if (panel_type != PANEL_TYPE_NONE) {
+ if (g_strcmp0 (new_name, "") != 0) {
+ /* a Panel process is started. */
+ BusConnection *connection;
+ BusInputContext *context = NULL;
+-
+- if (ibus->panel != NULL) {
+- ibus_proxy_destroy ((IBusProxy *) ibus->panel);
+- /* panel should be NULL after destroy. See _panel_destroy_cb for details. */
+- g_assert (ibus->panel == NULL);
++ BusPanelProxy **panel = (panel_type == PANEL_TYPE_PANEL) ?
++ &ibus->panel : &ibus->extension;
++
++ if (*panel != NULL) {
++ ibus_proxy_destroy ((IBusProxy *)(*panel));
++ /* panel should be NULL after destroy. See _panel_destroy_cb
++ * for details. */
++ g_assert (*panel == NULL);
+ }
+
+ connection = bus_dbus_impl_get_connection_by_name (BUS_DEFAULT_DBUS, new_name);
+ g_return_if_fail (connection != NULL);
+
+- ibus->panel = bus_panel_proxy_new (connection);
++ *panel = bus_panel_proxy_new (connection, panel_type);
+
+- g_signal_connect (ibus->panel,
++ g_signal_connect (*panel,
+ "destroy",
+ G_CALLBACK (_panel_destroy_cb),
+ ibus);
++ g_signal_connect (*panel,
++ "panel-extension",
++ G_CALLBACK (_panel_panel_extension_cb),
++ ibus);
+
+ if (ibus->focused_context != NULL) {
+ context = ibus->focused_context;
+@@ -355,14 +396,13 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus,
+ if (context != NULL) {
+ BusEngineProxy *engine;
+
+- bus_panel_proxy_focus_in (ibus->panel, context);
++ bus_panel_proxy_focus_in (*panel, context);
+
+ engine = bus_input_context_get_engine (context);
+ if (engine != NULL) {
+ IBusPropList *prop_list =
+ bus_engine_proxy_get_properties (engine);
+- bus_panel_proxy_register_properties (ibus->panel,
+- prop_list);
++ bus_panel_proxy_register_properties (*panel, prop_list);
+ }
+ }
+ }
+@@ -403,6 +443,7 @@ bus_ibus_impl_init (BusIBusImpl *ibus)
+ ibus->contexts = NULL;
+ ibus->focused_context = NULL;
+ ibus->panel = NULL;
++ ibus->extension = NULL;
+
+ ibus->keymap = ibus_keymap_get ("us");
+
+@@ -635,6 +676,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus,
+
+ if (ibus->panel != NULL)
+ bus_panel_proxy_focus_out (ibus->panel, ibus->focused_context);
++ if (ibus->extension != NULL)
++ bus_panel_proxy_focus_out (ibus->extension, ibus->focused_context);
+
+ bus_input_context_get_content_type (ibus->focused_context,
+ &purpose, &hints);
+@@ -658,6 +701,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus,
+
+ if (ibus->panel != NULL)
+ bus_panel_proxy_focus_in (ibus->panel, context);
++ if (ibus->extension != NULL)
++ bus_panel_proxy_focus_in (ibus->extension, context);
+ }
+
+ if (engine != NULL)
+@@ -846,8 +891,13 @@ _context_destroy_cb (BusInputContext *context,
+ bus_ibus_impl_set_focused_context (ibus, NULL);
+
+ if (ibus->panel &&
+- bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS)
++ bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
+ bus_panel_proxy_destroy_context (ibus->panel, context);
++ }
++ if (ibus->extension &&
++ bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) {
++ bus_panel_proxy_destroy_context (ibus->extension, context);
++ }
+
+ ibus->contexts = g_list_remove (ibus->contexts, context);
+ g_object_unref (context);
+diff --git a/bus/main.c b/bus/main.c
+index 6ad60179..5b2589b1 100644
+--- a/bus/main.c
++++ b/bus/main.c
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2013 Red Hat, Inc.
++ * Copyright (C) 2013-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -42,6 +43,7 @@ static gboolean xim = FALSE;
+ static gboolean replace = FALSE;
+ static gboolean restart = FALSE;
+ static gchar *panel = "default";
++static gchar *panel_extension = "default";
+ static gchar *config = "default";
+ static gchar *desktop = "gnome";
+
+@@ -60,6 +62,7 @@ static const GOptionEntry entries[] =
+ { "xim", 'x', 0, G_OPTION_ARG_NONE, &xim, "execute ibus XIM server.", NULL },
+ { "desktop", 'n', 0, G_OPTION_ARG_STRING, &desktop, "specify the name of desktop session. [default=gnome]", "name" },
+ { "panel", 'p', 0, G_OPTION_ARG_STRING, &panel, "specify the cmdline of panel program. pass 'disable' not to start a panel program.", "cmdline" },
++ { "panel-extension", 'E', 0, G_OPTION_ARG_STRING, &panel_extension, "specify the cmdline of panel extension program. pass 'disable' not to start an extension program.", "cmdline" },
+ { "config", 'c', 0, G_OPTION_ARG_STRING, &config, "specify the cmdline of config program. pass 'disable' not to start a config program.", "cmdline" },
+ { "address", 'a', 0, G_OPTION_ARG_STRING, &g_address, "specify the address of ibus daemon.", "address" },
+ { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "if there is an old ibus-daemon is running, it will be replaced.", NULL },
+@@ -268,7 +271,27 @@ main (gint argc, gchar **argv)
+ if (!execute_cmdline (panel))
+ exit (-1);
+ }
++
++#ifdef EMOJI_DICT
++ if (g_strcmp0 (panel_extension, "default") == 0) {
++ BusComponent *component;
++ component = bus_ibus_impl_lookup_component_by_name (
++ BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION);
++ if (component) {
++ bus_component_set_restart (component, restart);
++ }
++ if (component == NULL ||
++ !bus_component_start (component, g_verbose)) {
++ g_printerr ("Can not execute default panel program\n");
++ exit (-1);
++ }
++ } else if (g_strcmp0 (panel_extension, "disable") != 0 &&
++ g_strcmp0 (panel_extension, "") != 0) {
++ if (!execute_cmdline (panel_extension))
++ exit (-1);
++ }
+ }
++#endif
+
+ /* execute ibus xim server */
+ if (xim) {
+diff --git a/bus/marshalers.list b/bus/marshalers.list
+index c032cdaa..437c6fee 100644
+--- a/bus/marshalers.list
++++ b/bus/marshalers.list
+@@ -12,4 +12,5 @@ VOID:STRING
+ VOID:STRING,INT
+ VOID:UINT,UINT
+ VOID:UINT,UINT,UINT
++VOID:VARIANT
+ VOID:VOID
+diff --git a/bus/panelproxy.c b/bus/panelproxy.c
+index 8381d7dc..c3908fcf 100644
+--- a/bus/panelproxy.c
++++ b/bus/panelproxy.c
+@@ -2,8 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2014 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+- * Copyright (C) 2008-2014 Red Hat, Inc.
++ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -51,6 +51,7 @@ enum {
+ PROPERTY_SHOW,
+ PROPERTY_HIDE,
+ COMMIT_TEXT,
++ PANEL_EXTENSION,
+ LAST_SIGNAL,
+ };
+
+@@ -59,6 +60,7 @@ struct _BusPanelProxy {
+
+ /* instance members */
+ BusInputContext *focused_context;
++ PanelType panel_type;
+ };
+
+ struct _BusPanelProxyClass {
+@@ -110,22 +112,39 @@ static void bus_panel_proxy_commit_text
+ G_DEFINE_TYPE(BusPanelProxy, bus_panel_proxy, IBUS_TYPE_PROXY)
+
+ BusPanelProxy *
+-bus_panel_proxy_new (BusConnection *connection)
++bus_panel_proxy_new (BusConnection *connection,
++ PanelType panel_type)
+ {
++ const gchar *path = NULL;
++ GObject *obj;
++ BusPanelProxy *panel;
++
+ g_assert (BUS_IS_CONNECTION (connection));
+
+- GObject *obj;
++ switch (panel_type) {
++ case PANEL_TYPE_PANEL:
++ path = IBUS_PATH_PANEL;
++ break;
++ case PANEL_TYPE_EXTENSION:
++ path = IBUS_PATH_PANEL_EXTENSION;
++ break;
++ default:
++ g_return_val_if_reached (NULL);
++ }
++
+ obj = g_initable_new (BUS_TYPE_PANEL_PROXY,
+ NULL,
+ NULL,
+- "g-object-path", IBUS_PATH_PANEL,
++ "g-object-path", path,
+ "g-interface-name", IBUS_INTERFACE_PANEL,
+ "g-connection", bus_connection_get_dbus_connection (connection),
+ "g-default-timeout", g_gdbus_timeout,
+ "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL);
+
+- return BUS_PANEL_PROXY (obj);
++ panel = BUS_PANEL_PROXY (obj);
++ panel->panel_type = panel_type;
++ return panel;
+ }
+
+ static void
+@@ -231,6 +250,16 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class)
+ bus_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ IBUS_TYPE_TEXT);
++
++ panel_signals[PANEL_EXTENSION] =
++ g_signal_new (I_("panel-extension"),
++ G_TYPE_FROM_CLASS (class),
++ G_SIGNAL_RUN_LAST,
++ 0,
++ NULL, NULL,
++ bus_marshal_VOID__VARIANT,
++ G_TYPE_NONE, 1,
++ G_TYPE_VARIANT);
+ }
+
+ static void
+@@ -337,6 +366,15 @@ bus_panel_proxy_g_signal (GDBusProxy *proxy,
+ return;
+ }
+
++ if (g_strcmp0 ("PanelExtension", signal_name) == 0) {
++ if (panel->panel_type != PANEL_TYPE_PANEL) {
++ g_warning ("Wrong signal");
++ return;
++ }
++ g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, parameters);
++ return;
++ }
++
+ /* shound not be reached */
+ g_return_if_reached ();
+ }
+@@ -832,3 +870,10 @@ bus_panel_proxy_destroy_context (BusPanelProxy *panel,
+
+ g_object_unref (context);
+ }
++
++PanelType
++bus_panel_proxy_get_panel_type (BusPanelProxy *panel)
++{
++ g_assert (BUS_IS_PANEL_PROXY (panel));
++ return panel->panel_type;
++}
+diff --git a/bus/panelproxy.h b/bus/panelproxy.h
+index 5002f86d..b5a7af17 100644
+--- a/bus/panelproxy.h
++++ b/bus/panelproxy.h
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2014 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2014 Red Hat, Inc.
++ * Copyright (C) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -50,12 +51,25 @@
+
+ G_BEGIN_DECLS
+
++typedef enum
++{
++ PANEL_TYPE_NONE,
++ PANEL_TYPE_PANEL,
++ PANEL_TYPE_EXTENSION
++} PanelType;
++
+ typedef struct _BusPanelProxy BusPanelProxy;
+ typedef struct _BusPanelProxyClass BusPanelProxyClass;
+
+ GType bus_panel_proxy_get_type (void);
+-BusPanelProxy *bus_panel_proxy_new (BusConnection *connection);
++BusPanelProxy *bus_panel_proxy_new (BusConnection *connection,
++ PanelType panel_type);
+
++gboolean bus_panel_proxy_send_signal (BusPanelProxy *panel,
++ const gchar *interface_name,
++ const gchar *signal_name,
++ GVariant *parameters,
++ GError **error);
+ /* functions that invoke D-Bus methods of the panel component. */
+ void bus_panel_proxy_focus_in (BusPanelProxy *panel,
+ BusInputContext *context);
+@@ -119,6 +133,8 @@ void bus_panel_proxy_set_content_type
+ (BusPanelProxy *panel,
+ guint purpose,
+ guint hints);
++PanelType bus_panel_proxy_get_panel_type
++ (BusPanelProxy *panel);
+ G_END_DECLS
+ #endif
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 1ba418d8..72ec05ab 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -3,7 +3,7 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ # Copyright (c) 2007-2017 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+@@ -103,6 +103,7 @@ ibus_sources = \
+ ibustext.c \
+ ibusunicode.c \
+ ibusutil.c \
++ ibusxevent.c \
+ ibusxml.c \
+ $(NULL)
+ libibus_1_0_la_SOURCES = \
+@@ -155,6 +156,7 @@ ibus_headers = \
+ ibustypes.h \
+ ibusunicode.h \
+ ibusutil.h \
++ ibusxevent.h \
+ ibusxml.h \
+ $(NULL)
+ ibusincludedir = $(includedir)/ibus-@IBUS_API_VERSION@
+diff --git a/src/ibus.h b/src/ibus.h
+index 8011729f..b15dded9 100644
+--- a/src/ibus.h
++++ b/src/ibus.h
+@@ -59,6 +59,7 @@
+ #include <ibusregistry.h>
+ #include <ibusemoji.h>
+ #include <ibusunicode.h>
++#include <ibusxevent.h>
+
+ #ifndef IBUS_DISABLE_DEPRECATED
+ #include <ibuskeysyms-compat.h>
+diff --git a/src/ibusmarshalers.list b/src/ibusmarshalers.list
+index 918bc7f7..8d91937e 100644
+--- a/src/ibusmarshalers.list
++++ b/src/ibusmarshalers.list
+@@ -24,4 +24,5 @@ VOID:STRING,STRING,STRING
+ VOID:UINT
+ VOID:UINT,POINTER
+ VOID:POINTER,UINT
++VOID:VARIANT
+ OBJECT:STRING
+diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c
+index 33949fa1..f37b91c3 100644
+--- a/src/ibuspanelservice.c
++++ b/src/ibuspanelservice.c
+@@ -25,6 +25,10 @@
+ #include "ibusmarshalers.h"
+ #include "ibusinternal.h"
+
++#define IBUS_PANEL_SERVICE_GET_PRIVATE(o) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_PANEL_SERVICE, \
++ IBusPanelServicePrivate))
++
+ enum {
+ UPDATE_PREEDIT_TEXT,
+ UPDATE_AUXILIARY_TEXT,
+@@ -52,6 +56,7 @@ enum {
+ STATE_CHANGED,
+ DESTROY_CONTEXT,
+ SET_CONTENT_TYPE,
++ PANEL_EXTENSION_RECEIVED,
+ LAST_SIGNAL,
+ };
+
+@@ -146,6 +151,9 @@ static void ibus_panel_service_set_content_type
+ (IBusPanelService *panel,
+ guint purpose,
+ guint hints);
++static void ibus_panel_service_panel_extension_received
++ (IBusPanelService *panel,
++ GVariant *data);
+
+ G_DEFINE_TYPE (IBusPanelService, ibus_panel_service, IBUS_TYPE_SERVICE)
+
+@@ -212,6 +220,9 @@ static const gchar introspection_xml[] =
+ " <arg direction='in' type='u' name='purpose' />"
+ " <arg direction='in' type='u' name='hints' />"
+ " </method>"
++ " <method name='PanelExtensionReceived'>"
++ " <arg direction='in' type='v' name='data' />"
++ " </method>"
+ /* Signals */
+ " <signal name='CursorUp' />"
+ " <signal name='CursorDown' />"
+@@ -235,6 +246,9 @@ static const gchar introspection_xml[] =
+ " <signal name='CommitText'>"
+ " <arg type='v' name='text' />"
+ " </signal>"
++ " <signal name='PanelExtension'>"
++ " <arg type='v' name='data' />"
++ " </signal>"
+ " </interface>"
+ "</node>";
+
+@@ -274,6 +288,8 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
+ class->update_preedit_text = ibus_panel_service_update_preedit_text;
+ class->update_property = ibus_panel_service_update_property;
+ class->set_content_type = ibus_panel_service_set_content_type;
++ class->panel_extension_received =
++ ibus_panel_service_panel_extension_received;
+
+ class->cursor_down_lookup_table = ibus_panel_service_not_implemented;
+ class->cursor_up_lookup_table = ibus_panel_service_not_implemented;
+@@ -891,6 +907,30 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
+ 2,
+ G_TYPE_UINT,
+ G_TYPE_UINT);
++
++ /**
++ * IBusPanelService::panel-extension-received:
++ * @panel: An #IBusPanelService
++ * @data: A #GVariant
++ *
++ * Emitted when the client application get the ::panel-extension-received.
++ * Implement the member function
++ * IBusPanelServiceClass::panel_extension_received in extended class to
++ * receive this signal.
++ *
++ * <note><para>Argument @user_data is ignored in this function.</para>
++ * </note>
++ */
++ panel_signals[PANEL_EXTENSION_RECEIVED] =
++ g_signal_new (I_("panel-extension-received"),
++ G_TYPE_FROM_CLASS (gobject_class),
++ G_SIGNAL_RUN_LAST,
++ G_STRUCT_OFFSET (IBusPanelServiceClass, panel_extension_received),
++ NULL, NULL,
++ _ibus_marshal_VOID__VARIANT,
++ G_TYPE_NONE,
++ 1,
++ G_TYPE_VARIANT);
+ }
+
+ static void
+@@ -1088,6 +1128,24 @@ ibus_panel_service_service_method_call (IBusService *service,
+ return;
+ }
+
++ if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) {
++ GVariant *variant = NULL;
++ g_variant_get (parameters, "(v)", &variant);
++ if (variant == NULL) {
++ g_dbus_method_invocation_return_error (
++ invocation,
++ G_DBUS_ERROR,
++ G_DBUS_ERROR_FAILED,
++ "PanelExtensionReceived method gives NULL");
++ return;
++ }
++ g_signal_emit (panel, panel_signals[PANEL_EXTENSION_RECEIVED], 0,
++ variant);
++ g_variant_unref (variant);
++ g_dbus_method_invocation_return_value (invocation, NULL);
++ return;
++ }
++
+ const static struct {
+ const gchar *name;
+ const gint signal_id;
+@@ -1259,6 +1317,13 @@ ibus_panel_service_set_content_type (IBusPanelService *panel,
+ ibus_panel_service_not_implemented(panel);
+ }
+
++static void
++ibus_panel_service_panel_extension_received (IBusPanelService *panel,
++ GVariant *data)
++{
++ ibus_panel_service_not_implemented(panel);
++}
++
+ IBusPanelService *
+ ibus_panel_service_new (GDBusConnection *connection)
+ {
+@@ -1347,6 +1412,21 @@ ibus_panel_service_commit_text (IBusPanelService *panel,
+ }
+ }
+
++void
++ibus_panel_service_panel_extension (IBusPanelService *panel,
++ GVariant *variant)
++{
++ g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
++ g_return_if_fail (variant);
++
++ ibus_service_emit_signal ((IBusService *) panel,
++ NULL,
++ IBUS_INTERFACE_PANEL,
++ "PanelExtension",
++ g_variant_new ("(v)", variant),
++ NULL);
++}
++
+ #define DEFINE_FUNC(name, Name) \
+ void \
+ ibus_panel_service_##name (IBusPanelService *panel) \
+@@ -1364,4 +1444,3 @@ DEFINE_FUNC (cursor_up, CursorUp)
+ DEFINE_FUNC (page_down, PageDown)
+ DEFINE_FUNC (page_up, PageUp)
+ #undef DEFINE_FUNC
+-
+diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h
+index a5b13c73..60ef842b 100644
+--- a/src/ibuspanelservice.h
++++ b/src/ibuspanelservice.h
+@@ -2,7 +2,7 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (c) 2009-2014 Google Inc. All rights reserved.
+- * Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -128,6 +128,9 @@ struct _IBusPanelServiceClass {
+ gint y,
+ gint w,
+ gint h);
++ void (* panel_extension_received)
++ (IBusPanelService *panel,
++ GVariant *data);
+
+ /*< private >*/
+ /* padding */
+@@ -242,5 +245,15 @@ void ibus_panel_service_property_hide (IBusPanelService *panel,
+ void ibus_panel_service_commit_text (IBusPanelService *panel,
+ IBusText *text);
+
++/**
++ * ibus_panel_service_panel_extension:
++ * @panel: An #IBusPanelService
++ * @data: (transfer full): A #GVariant data which is sent to a panel extension.
++ *
++ * Notify that a data is sent
++ * by sending a "PanelExtension" message to IBus panel extension service.
++ */
++void ibus_panel_service_panel_extension (IBusPanelService *panel,
++ GVariant *data);
+ G_END_DECLS
+ #endif
+diff --git a/src/ibusserializable.c b/src/ibusserializable.c
+index d7f867f4..a377b613 100644
+--- a/src/ibusserializable.c
++++ b/src/ibusserializable.c
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2010 Red Hat, Inc.
++ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -251,7 +252,7 @@ ibus_serializable_copy (IBusSerializable *object)
+ }
+
+ GVariant *
+-ibus_serializable_serialize (IBusSerializable *object)
++ibus_serializable_serialize_object (IBusSerializable *object)
+ {
+ g_return_val_if_fail (IBUS_IS_SERIALIZABLE (object), FALSE);
+ gboolean retval;
+@@ -267,7 +268,7 @@ ibus_serializable_serialize (IBusSerializable *object)
+ }
+
+ IBusSerializable *
+-ibus_serializable_deserialize (GVariant *variant)
++ibus_serializable_deserialize_object (GVariant *variant)
+ {
+ g_return_val_if_fail (variant != NULL, NULL);
+
+diff --git a/src/ibusserializable.h b/src/ibusserializable.h
+index 4327eaee..102de1bd 100644
+--- a/src/ibusserializable.h
++++ b/src/ibusserializable.h
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2013 Red Hat, Inc.
++ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -248,10 +249,10 @@ void ibus_serializable_remove_qattachment
+ *
+ * See also: IBusSerializableCopyFunc().
+ */
+-IBusSerializable *ibus_serializable_copy (IBusSerializable *serializable);
++IBusSerializable *ibus_serializable_copy (IBusSerializable *serializable);
+
+ /**
+- * ibus_serializable_serialize:
++ * ibus_serializable_serialize_object:
+ * @serializable: An #IBusSerializable.
+ *
+ * Serialize an #IBusSerializable to a #GVariant.
+@@ -261,10 +262,11 @@ IBusSerializable *ibus_serializable_copy (IBusSerializable *ser
+ *
+ * See also: IBusSerializableCopyFunc().
+ */
+-GVariant *ibus_serializable_serialize (IBusSerializable *serializable);
++GVariant *ibus_serializable_serialize_object
++ (IBusSerializable *serializable);
+
+ /**
+- * ibus_serializable_deserialize:
++ * ibus_serializable_deserialize_object:
+ * @variant: A #GVariant.
+ *
+ * Deserialize a #GVariant to an #IBusSerializable/
+@@ -274,7 +276,11 @@ GVariant *ibus_serializable_serialize (IBusSerializable *ser
+ *
+ * See also: IBusSerializableCopyFunc().
+ */
+-IBusSerializable *ibus_serializable_deserialize (GVariant *variant);
++IBusSerializable *ibus_serializable_deserialize_object
++ (GVariant *variant);
++
++#define ibus_serializable_serialize ibus_serializable_serialize_object
++#define ibus_serializable_deserialize ibus_serializable_deserialize_object
+
+ G_END_DECLS
+ #endif
+diff --git a/src/ibusshare.c b/src/ibusshare.c
+index b793a962..d7724a6b 100644
+--- a/src/ibusshare.c
++++ b/src/ibusshare.c
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2010 Red Hat, Inc.
++ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -287,6 +288,7 @@ ibus_init (void)
+ IBUS_TYPE_ENGINE_DESC;
+ IBUS_TYPE_OBSERVED_PATH;
+ IBUS_TYPE_REGISTRY;
++ IBUS_TYPE_X_EVENT;
+ }
+
+ static GMainLoop *main_loop = NULL;
+diff --git a/src/ibusshare.h b/src/ibusshare.h
+index f3e2011e..757d915b 100644
+--- a/src/ibusshare.h
++++ b/src/ibusshare.h
+@@ -2,7 +2,8 @@
+ /* vim:set et sts=4: */
+ /* ibus - The Input Bus
+ * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
+- * Copyright (C) 2008-2013 Red Hat, Inc.
++ * Copyright (C) 2015-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2008-2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -65,6 +66,13 @@
+ */
+ #define IBUS_SERVICE_PANEL "org.freedesktop.IBus.Panel"
+
++/**
++ * IBUS_SERVICE_PANEL_EXTENSION:
++ *
++ * Address of IBus panel extension service.
++ */
++#define IBUS_SERVICE_PANEL_EXTENSION "org.freedesktop.IBus.Panel.Extension"
++
+ /**
+ * IBUS_SERVICE_CONFIG:
+ *
+@@ -100,6 +108,13 @@
+ */
+ #define IBUS_PATH_PANEL "/org/freedesktop/IBus/Panel"
+
++/**
++ * IBUS_PATH_PANEL_EXTENSION:
++ *
++ * D-Bus path for IBus panel.
++ */
++#define IBUS_PATH_PANEL_EXTENSION "/org/freedesktop/IBus/Panel/Extension"
++
+ /**
+ * IBUS_PATH_CONFIG:
+ *
+diff --git a/src/ibusxevent.c b/src/ibusxevent.c
+new file mode 100644
+index 00000000..dea80272
+--- /dev/null
++++ b/src/ibusxevent.c
+@@ -0,0 +1,1004 @@
++/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
++/* vim:set et sts=4: */
++/* ibus - The Input Bus
++ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2018 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA
++ */
++#include "ibusinternal.h"
++#include "ibusxevent.h"
++
++#define IBUS_X_EVENT_VERSION 1
++#define IBUS_X_EVENT_GET_PRIVATE(o) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_X_EVENT, IBusXEventPrivate))
++
++enum {
++ PROP_0,
++ PROP_VERSION,
++ PROP_EVENT_TYPE,
++ PROP_WINDOW,
++ PROP_SEND_EVENT,
++ PROP_SERIAL,
++ PROP_TIME,
++ PROP_STATE,
++ PROP_KEYVAL,
++ PROP_LENGTH,
++ PROP_STRING,
++ PROP_HARDWARE_KEYCODE,
++ PROP_GROUP,
++ PROP_IS_MODIFIER,
++ PROP_ROOT,
++ PROP_SUBWINDOW,
++ PROP_X,
++ PROP_Y,
++ PROP_X_ROOT,
++ PROP_Y_ROOT,
++ PROP_SAME_SCREEN,
++ PROP_PURPOSE
++};
++
++
++struct _IBusXEventPrivate {
++ guint version;
++ guint32 time;
++ guint state;
++ guint keyval;
++ gint length;
++ gchar *string;
++ guint16 hardware_keycode;
++ guint8 group;
++ gboolean is_modifier;
++ guint root;
++ guint subwindow;
++ gint x;
++ gint y;
++ gint x_root;
++ gint y_root;
++ gboolean same_screen;
++ gchar *purpose;
++};
++
++/* functions prototype */
++static void ibus_x_event_destroy (IBusXEvent *event);
++static void ibus_x_event_set_property (IBusXEvent *event,
++ guint prop_id,
++ const GValue *value,
++ GParamSpec *pspec);
++static void ibus_x_event_get_property (IBusXEvent *event,
++ guint prop_id,
++ GValue *value,
++ GParamSpec *pspec);
++static gboolean ibus_x_event_serialize (IBusXEvent *event,
++ GVariantBuilder *builder);
++static gint ibus_x_event_deserialize (IBusXEvent *event,
++ GVariant *variant);
++static gboolean ibus_x_event_copy (IBusXEvent *dest,
++ const IBusXEvent *src);
++
++G_DEFINE_TYPE (IBusXEvent, ibus_x_event, IBUS_TYPE_SERIALIZABLE)
++
++static void
++ibus_x_event_class_init (IBusXEventClass *class)
++{
++ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
++ IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
++ IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
++
++ gobject_class->set_property =
++ (GObjectSetPropertyFunc) ibus_x_event_set_property;
++ gobject_class->get_property =
++ (GObjectGetPropertyFunc) ibus_x_event_get_property;
++
++ object_class->destroy = (IBusObjectDestroyFunc) ibus_x_event_destroy;
++
++ serializable_class->serialize =
++ (IBusSerializableSerializeFunc) ibus_x_event_serialize;
++ serializable_class->deserialize =
++ (IBusSerializableDeserializeFunc) ibus_x_event_deserialize;
++ serializable_class->copy =
++ (IBusSerializableCopyFunc) ibus_x_event_copy;
++
++ /* install properties */
++ /**
++ * IBusXEvent:version:
++ *
++ * Version of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_VERSION,
++ g_param_spec_uint ("version",
++ "version",
++ "version",
++ 0,
++ G_MAXUINT32,
++ IBUS_X_EVENT_VERSION,
++ G_PARAM_READABLE));
++
++ /**
++ * IBusXEvent:event-type:
++ *
++ * IBusXEventType of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_EVENT_TYPE,
++ g_param_spec_int ("event-type",
++ "event type",
++ "event type",
++ -1,
++ G_MAXINT32,
++ -1,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:window:
++ *
++ * window of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_WINDOW,
++ g_param_spec_uint ("window",
++ "window",
++ "window",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:send-event:
++ *
++ * send_event of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_SEND_EVENT,
++ g_param_spec_int ("send-event",
++ "send event",
++ "send event",
++ 0,
++ G_MAXINT8,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:serial:
++ *
++ * serial of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_SERIAL,
++ g_param_spec_ulong ("serial",
++ "serial",
++ "serial",
++ 0,
++ G_MAXUINT64,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:time:
++ *
++ * time of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_TIME,
++ g_param_spec_uint ("time",
++ "time",
++ "time",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:state:
++ *
++ * state of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_STATE,
++ g_param_spec_uint ("state",
++ "state",
++ "state",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:keyval:
++ *
++ * keyval of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_KEYVAL,
++ g_param_spec_uint ("keyval",
++ "keyval",
++ "keyval",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:length:
++ *
++ * keyval of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_LENGTH,
++ g_param_spec_int ("length",
++ "length",
++ "length",
++ -1,
++ G_MAXINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:string:
++ *
++ * string of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_STRING,
++ g_param_spec_string ("string",
++ "string",
++ "string",
++ "",
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:hardware-keycode:
++ *
++ * hardware keycode of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_HARDWARE_KEYCODE,
++ g_param_spec_uint ("hardware-keycode",
++ "hardware keycode",
++ "hardware keycode",
++ 0,
++ G_MAXUINT16,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:group:
++ *
++ * group of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_GROUP,
++ g_param_spec_uint ("group",
++ "group",
++ "group",
++ 0,
++ G_MAXUINT8,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:is-modifier:
++ *
++ * is_modifier of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_IS_MODIFIER,
++ g_param_spec_boolean ("is-modifier",
++ "is modifier",
++ "is modifier",
++ FALSE,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:root:
++ *
++ * root window of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_ROOT,
++ g_param_spec_uint ("root",
++ "root",
++ "root",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:subwindow:
++ *
++ * subwindow of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_SUBWINDOW,
++ g_param_spec_uint ("subwindow",
++ "subwindow",
++ "subwindow",
++ 0,
++ G_MAXUINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:x:
++ *
++ * x of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_X,
++ g_param_spec_int ("x",
++ "x",
++ "x",
++ G_MININT32,
++ G_MAXINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:y:
++ *
++ * x of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_Y,
++ g_param_spec_int ("y",
++ "y",
++ "y",
++ G_MININT32,
++ G_MAXINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:x-root:
++ *
++ * root-x of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_X_ROOT,
++ g_param_spec_int ("x-root",
++ "x root",
++ "x root",
++ G_MININT32,
++ G_MAXINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:y-root:
++ *
++ * root-y of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_Y_ROOT,
++ g_param_spec_int ("y-root",
++ "y root",
++ "y root",
++ G_MININT32,
++ G_MAXINT32,
++ 0,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:same-screen:
++ *
++ * same_screen of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_SAME_SCREEN,
++ g_param_spec_boolean ("same-screen",
++ "same screen",
++ "same screen",
++ TRUE,
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ /**
++ * IBusXEvent:purpose:
++ *
++ * purpose of this IBusXEvent.
++ */
++ g_object_class_install_property (gobject_class,
++ PROP_PURPOSE,
++ g_param_spec_string ("purpose",
++ "purpose",
++ "purpose",
++ "",
++ G_PARAM_READWRITE |
++ G_PARAM_CONSTRUCT_ONLY));
++
++ g_type_class_add_private (class, sizeof (IBusXEventPrivate));
++}
++
++static void
++ibus_x_event_init (IBusXEvent *event)
++{
++ event->priv = IBUS_X_EVENT_GET_PRIVATE (event);
++ event->priv->version = IBUS_X_EVENT_VERSION;
++}
++
++static void
++ibus_x_event_destroy (IBusXEvent *event)
++{
++ g_clear_pointer (&event->priv->string, g_free);
++
++ IBUS_OBJECT_CLASS(ibus_x_event_parent_class)->destroy (IBUS_OBJECT (event));
++}
++
++static void
++ibus_x_event_set_property (IBusXEvent *event,
++ guint prop_id,
++ const GValue *value,
++ GParamSpec *pspec)
++{
++ IBusXEventPrivate *priv = event->priv;
++
++ switch (prop_id) {
++ case PROP_EVENT_TYPE:
++ event->event_type = g_value_get_int (value);
++ break;
++ case PROP_WINDOW:
++ event->window = g_value_get_uint (value);
++ break;
++ case PROP_SEND_EVENT:
++ event->send_event = g_value_get_int (value);
++ break;
++ case PROP_SERIAL:
++ event->serial = g_value_get_ulong (value);
++ break;
++ case PROP_TIME:
++ priv->time = g_value_get_uint (value);
++ break;
++ case PROP_STATE:
++ priv->state = g_value_get_uint (value);
++ break;
++ case PROP_KEYVAL:
++ priv->keyval = g_value_get_uint (value);
++ break;
++ case PROP_LENGTH:
++ priv->length = g_value_get_int (value);
++ break;
++ case PROP_STRING:
++ g_free (priv->string);
++ priv->string = g_value_dup_string (value);
++ break;
++ case PROP_HARDWARE_KEYCODE:
++ priv->hardware_keycode = g_value_get_uint (value);
++ break;
++ case PROP_GROUP:
++ priv->group = g_value_get_uint (value);
++ break;
++ case PROP_IS_MODIFIER:
++ priv->is_modifier = g_value_get_boolean (value);
++ break;
++ case PROP_ROOT:
++ priv->root = g_value_get_uint (value);
++ break;
++ case PROP_SUBWINDOW:
++ priv->subwindow = g_value_get_uint (value);
++ break;
++ case PROP_X:
++ priv->x = g_value_get_int (value);
++ break;
++ case PROP_Y:
++ priv->y = g_value_get_int (value);
++ break;
++ case PROP_X_ROOT:
++ priv->x_root = g_value_get_int (value);
++ break;
++ case PROP_Y_ROOT:
++ priv->y_root = g_value_get_int (value);
++ break;
++ case PROP_SAME_SCREEN:
++ priv->same_screen = g_value_get_boolean (value);
++ break;
++ case PROP_PURPOSE:
++ g_free (priv->purpose);
++ priv->purpose = g_value_dup_string (value);
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
++ }
++}
++
++static void
++ibus_x_event_get_property (IBusXEvent *event,
++ guint prop_id,
++ GValue *value,
++ GParamSpec *pspec)
++{
++ IBusXEventPrivate *priv = event->priv;
++ switch (prop_id) {
++ case PROP_VERSION:
++ g_value_set_uint (value, priv->version);
++ break;
++ case PROP_EVENT_TYPE:
++ g_value_set_int (value, event->event_type);
++ break;
++ case PROP_WINDOW:
++ g_value_set_uint (value, event->window);
++ break;
++ case PROP_SEND_EVENT:
++ g_value_set_int (value, event->send_event);
++ break;
++ case PROP_SERIAL:
++ g_value_set_ulong (value, event->serial);
++ break;
++ case PROP_TIME:
++ g_value_set_uint (value, priv->time);
++ break;
++ case PROP_STATE:
++ g_value_set_uint (value, priv->state);
++ break;
++ case PROP_KEYVAL:
++ g_value_set_uint (value, priv->keyval);
++ break;
++ case PROP_LENGTH:
++ g_value_set_int (value, priv->length);
++ break;
++ case PROP_STRING:
++ g_value_set_string (value, priv->string);
++ break;
++ case PROP_HARDWARE_KEYCODE:
++ g_value_set_uint (value, priv->hardware_keycode);
++ break;
++ case PROP_GROUP:
++ g_value_set_uint (value, priv->group);
++ break;
++ case PROP_IS_MODIFIER:
++ g_value_set_boolean (value, priv->is_modifier);
++ break;
++ case PROP_ROOT:
++ g_value_set_uint (value, priv->root);
++ break;
++ case PROP_SUBWINDOW:
++ g_value_set_uint (value, priv->subwindow);
++ break;
++ case PROP_X:
++ g_value_set_int (value, priv->x);
++ break;
++ case PROP_Y:
++ g_value_set_int (value, priv->y);
++ break;
++ case PROP_X_ROOT:
++ g_value_set_int (value, priv->x_root);
++ break;
++ case PROP_Y_ROOT:
++ g_value_set_int (value, priv->y_root);
++ break;
++ case PROP_SAME_SCREEN:
++ g_value_set_boolean (value, priv->same_screen);
++ break;
++ case PROP_PURPOSE:
++ g_value_set_string (value, priv->purpose);
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec);
++ }
++}
++
++static gboolean
++ibus_x_event_serialize (IBusXEvent *event,
++ GVariantBuilder *builder)
++{
++ gboolean retval;
++ IBusXEventPrivate *priv;
++
++ retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
++ serialize ((IBusSerializable *)event, builder);
++ g_return_val_if_fail (retval, FALSE);
++ /* End dict iter */
++
++ priv = event->priv;
++#define NOTNULL(s) ((s) != NULL ? (s) : "")
++ /* If you will add a new property, you can append it at the end and
++ * you should not change the serialized order of name, longname,
++ * description, ... because the order is also used in other applications
++ * likes ibus-qt. */
++ g_variant_builder_add (builder, "u", priv->version);
++ g_variant_builder_add (builder, "u", event->event_type);
++ g_variant_builder_add (builder, "u", event->window);
++ g_variant_builder_add (builder, "i", event->send_event);
++ g_variant_builder_add (builder, "t", event->serial);
++ g_variant_builder_add (builder, "u", priv->time);
++ g_variant_builder_add (builder, "u", priv->state);
++ g_variant_builder_add (builder, "u", priv->keyval);
++ g_variant_builder_add (builder, "i", priv->length);
++ g_variant_builder_add (builder, "s", NOTNULL (priv->string));
++ g_variant_builder_add (builder, "u", priv->hardware_keycode);
++ g_variant_builder_add (builder, "u", priv->group);
++ g_variant_builder_add (builder, "b", priv->is_modifier);
++ g_variant_builder_add (builder, "u", priv->root);
++ g_variant_builder_add (builder, "u", priv->subwindow);
++ g_variant_builder_add (builder, "i", priv->x);
++ g_variant_builder_add (builder, "i", priv->y);
++ g_variant_builder_add (builder, "i", priv->x_root);
++ g_variant_builder_add (builder, "i", priv->y_root);
++ g_variant_builder_add (builder, "b", priv->same_screen);
++ g_variant_builder_add (builder, "s", NOTNULL (priv->purpose));
++#undef NOTNULL
++
++ return TRUE;
++}
++
++static gint
++ibus_x_event_deserialize (IBusXEvent *event,
++ GVariant *variant)
++{
++ gint retval;
++ IBusXEventPrivate *priv;
++
++ retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
++ deserialize ((IBusSerializable *)event, variant);
++ g_return_val_if_fail (retval, 0);
++
++ priv = event->priv;
++ /* If you will add a new property, you can append it at the end and
++ * you should not change the serialized order of name, longname,
++ * description, ... because the order is also used in other applications
++ * likes ibus-qt. */
++ g_variant_get_child (variant, retval++, "u", &priv->version);
++ g_variant_get_child (variant, retval++, "u", &event->event_type);
++ g_variant_get_child (variant, retval++, "u", &event->window);
++ g_variant_get_child (variant, retval++, "i", &event->send_event);
++ g_variant_get_child (variant, retval++, "t", &event->serial);
++ g_variant_get_child (variant, retval++, "u", &priv->time);
++ g_variant_get_child (variant, retval++, "u", &priv->state);
++ g_variant_get_child (variant, retval++, "u", &priv->keyval);
++ g_variant_get_child (variant, retval++, "i", &priv->length);
++ ibus_g_variant_get_child_string (variant, retval++,
++ &priv->string);
++ g_variant_get_child (variant, retval++, "u", &priv->hardware_keycode);
++ g_variant_get_child (variant, retval++, "u", &priv->group);
++ g_variant_get_child (variant, retval++, "b", &priv->is_modifier);
++ g_variant_get_child (variant, retval++, "u", &priv->root);
++ g_variant_get_child (variant, retval++, "u", &priv->subwindow);
++ g_variant_get_child (variant, retval++, "i", &priv->x);
++ g_variant_get_child (variant, retval++, "i", &priv->y);
++ g_variant_get_child (variant, retval++, "i", &priv->x_root);
++ g_variant_get_child (variant, retval++, "i", &priv->y_root);
++ g_variant_get_child (variant, retval++, "b", &priv->same_screen);
++ ibus_g_variant_get_child_string (variant, retval++,
++ &priv->purpose);
++
++ return retval;
++}
++
++static gboolean
++ibus_x_event_copy (IBusXEvent *dest,
++ const IBusXEvent *src)
++{
++ gboolean retval;
++ IBusXEventPrivate *dest_priv = dest->priv;
++ IBusXEventPrivate *src_priv = src->priv;
++
++ retval = IBUS_SERIALIZABLE_CLASS (ibus_x_event_parent_class)->
++ copy ((IBusSerializable *)dest, (IBusSerializable *)src);
++ g_return_val_if_fail (retval, FALSE);
++
++ dest_priv->version = src_priv->version;
++ dest->event_type = src->event_type;
++ dest->window = src->window;
++ dest->send_event = src->send_event;
++ dest->serial = src->serial;
++ dest_priv->time = src_priv->time;
++ dest_priv->state = src_priv->state;
++ dest_priv->keyval = src_priv->keyval;
++ dest_priv->length = src_priv->length;
++ dest_priv->string = g_strdup (src_priv->string);
++ dest_priv->hardware_keycode = src_priv->hardware_keycode;
++ dest_priv->group = src_priv->group;
++ dest_priv->is_modifier = src_priv->is_modifier;
++ dest_priv->root = src_priv->root;
++ dest_priv->subwindow = src_priv->subwindow;
++ dest_priv->x = src_priv->x;
++ dest_priv->y = src_priv->y;
++ dest_priv->x_root = src_priv->x_root;
++ dest_priv->y_root = src_priv->y_root;
++ dest_priv->same_screen = src_priv->same_screen;
++ dest_priv->purpose = g_strdup (src_priv->purpose);
++
++ return TRUE;
++}
++
++IBusXEvent *
++ibus_x_event_new (const gchar *first_property_name,
++ ...)
++{
++ va_list var_args;
++ IBusXEvent *event;
++
++ va_start (var_args, first_property_name);
++ event = (IBusXEvent *) g_object_new_valist (IBUS_TYPE_X_EVENT,
++ first_property_name,
++ var_args);
++ va_end (var_args);
++ g_assert (event->priv->version != 0);
++ g_assert (event->event_type != IBUS_X_EVENT_NOTHING);
++ return event;
++}
++
++guint
++ibus_x_event_get_version (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ return event->priv->version;
++}
++
++IBusXEventType
++ibus_x_event_get_event_type (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ return event->event_type;
++}
++
++guint32
++ibus_x_event_get_window (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ return event->window;
++}
++
++gint8
++ibus_x_event_get_send_event (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), -1);
++ return event->send_event;
++}
++
++gulong
++ibus_x_event_get_serial (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ return event->serial;
++}
++
++guint32
++ibus_x_event_get_time (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->time;
++}
++
++guint
++ibus_x_event_get_state (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->state;
++}
++
++guint
++ibus_x_event_get_keyval (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->keyval;
++}
++
++gint
++ibus_x_event_get_length (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), -1);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (-1);
++ }
++ return event->priv->length;
++}
++
++const gchar *
++ibus_x_event_get_string (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), "");
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached ("");
++ }
++ return event->priv->string;
++}
++
++guint16
++ibus_x_event_get_hardware_keycode (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->hardware_keycode;
++}
++
++guint8
++ibus_x_event_get_group (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->group;
++}
++
++gboolean
++ibus_x_event_get_is_modifier (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->is_modifier;
++}
++
++guint32
++ibus_x_event_get_root (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->root;
++}
++
++guint32
++ibus_x_event_get_subwindow (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->subwindow;
++}
++
++gint
++ibus_x_event_get_x (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->x;
++}
++
++gint
++ibus_x_event_get_y (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->y;
++}
++
++gint
++ibus_x_event_get_x_root (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->x_root;
++}
++
++gint
++ibus_x_event_get_y_root (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), 0);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (0);
++ }
++ return event->priv->y_root;
++}
++
++gboolean
++ibus_x_event_get_same_screen (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), TRUE);
++ switch (event->event_type) {
++ case IBUS_X_EVENT_KEY_PRESS:
++ case IBUS_X_EVENT_KEY_RELEASE:
++ break;
++ default:
++ g_return_val_if_reached (TRUE);
++ }
++ return event->priv->same_screen;
++}
++
++const gchar *
++ibus_x_event_get_purpose (IBusXEvent *event)
++{
++ g_return_val_if_fail (IBUS_IS_X_EVENT (event), "");
++ return event->priv->purpose;
++}
+diff --git a/src/ibusxevent.h b/src/ibusxevent.h
+new file mode 100644
+index 00000000..f35f14e4
+--- /dev/null
++++ b/src/ibusxevent.h
+@@ -0,0 +1,294 @@
++/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
++/* vim:set et sts=4: */
++/* ibus - The Input Bus
++ * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ * Copyright (C) 2018 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA
++ */
++
++#if !defined (__IBUS_H_INSIDE__) && !defined (IBUS_COMPILATION)
++#error "Only <ibus.h> can be included directly"
++#endif
++
++#ifndef __IBUS_X_EVENT_H_
++#define __IBUS_X_EVENT_H_
++
++/**
++ * SECTION: ibusxevent
++ * @short_description: XEvent wrapper object
++ * @title: IBusXEvent
++ * @stability: Unstable
++ *
++ * An IBusXEvent provides a wrapper of XEvent.
++ *
++ * see_also: #IBusComponent, #IBusEngineDesc
++ */
++
++#include "ibusserializable.h"
++
++/*
++ * Type macros.
++ */
++
++/* define GOBJECT macros */
++#define IBUS_TYPE_X_EVENT \
++ (ibus_x_event_get_type ())
++#define IBUS_X_EVENT(obj) \
++ (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_X_EVENT, IBusXEvent))
++#define IBUS_X_EVENT_CLASS(klass) \
++ (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_X_EVENT, IBusXEventClass))
++#define IBUS_IS_X_EVENT(obj) \
++ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_X_EVENT))
++#define IBUS_IS_X_EVENT_CLASS(klass) \
++ (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_X_EVENT))
++#define IBUS_X_EVENT_GET_CLASS(obj) \
++ (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_X_EVENT, IBusXEventClass))
++
++G_BEGIN_DECLS
++
++typedef struct _IBusXEvent IBusXEvent;
++typedef struct _IBusXEventClass IBusXEventClass;
++typedef struct _IBusXEventPrivate IBusXEventPrivate;
++
++typedef enum {
++ IBUS_X_EVENT_NOTHING = -1,
++ IBUS_X_EVENT_KEY_PRESS = 0,
++ IBUS_X_EVENT_KEY_RELEASE = 1,
++ IBUS_X_EVENT_OTHER = 2,
++ IBUS_X_EVENT_EVENT_LAST /* helper variable for decls */
++} IBusXEventType;
++
++/**
++ * IBusXEvent:
++ * @type: event type
++ *
++ * IBusEngine properties.
++ */
++struct _IBusXEvent {
++ /*< private >*/
++ IBusSerializable parent;
++ IBusXEventPrivate *priv;
++
++ /* instance members */
++ /*< public >*/
++ IBusXEventType event_type;
++ guint window;
++ gint8 send_event;
++ gulong serial;
++};
++
++struct _IBusXEventClass {
++ /*< private >*/
++ IBusSerializableClass parent;
++
++ /* class members */
++ /*< public >*/
++
++ /*< private >*/
++ /* padding */
++ gpointer pdummy[10];
++};
++
++GType ibus_x_event_get_type (void);
++
++/**
++ * ibus_x_event_new:
++ * @first_property_name: Name of the first property.
++ * @...: the NULL-terminated arguments of the properties and values.
++ *
++ * Create a new #IBusXEvent.
++ *
++ * Returns: A newly allocated #IBusXEvent. E.g.
++ * ibus_x_event_new ("event-type", IBUS_X_EVENT_KEY_PRESS, NULL);
++ */
++IBusXEvent * ibus_x_event_new (const gchar
++ *first_property_name,
++ ...);
++
++/**
++ * ibus_x_event_get_version:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: Version of #IBusXEvent
++ */
++guint ibus_x_event_get_version (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_event_type:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: IBusXEventType of #IBusXEvent
++ */
++IBusXEventType ibus_x_event_get_event_type (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_window:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: XID of #IBusXEvent
++ */
++guint32 ibus_x_event_get_window (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_send_event:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: send_event of #IBusXEvent
++ */
++gint8 ibus_x_event_get_send_event (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_serial:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: serial of #IBusXEvent
++ */
++gulong ibus_x_event_get_serial (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_time:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: time of #IBusXEvent
++ */
++guint32 ibus_x_event_get_time (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_state:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: state of #IBusXEvent
++ */
++guint ibus_x_event_get_state (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_keyval:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: keyval of #IBusXEvent
++ */
++guint ibus_x_event_get_keyval (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_length:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: length of #IBusXEvent
++ */
++gint ibus_x_event_get_length (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_string:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: string of #IBusXEvent
++ */
++const gchar * ibus_x_event_get_string (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_hardware_keycode:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: hardware keycode of #IBusXEvent
++ */
++guint16 ibus_x_event_get_hardware_keycode
++ (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_group:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: group of #IBusXEvent
++ */
++guint8 ibus_x_event_get_group (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_is_modifier:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: is_modifier of #IBusXEvent
++ */
++gboolean ibus_x_event_get_is_modifier
++ (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_subwindow:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: subwindow of #IBusXEvent
++ */
++guint32 ibus_x_event_get_subwindow (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_root:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: root window of #IBusXEvent
++ */
++guint32 ibus_x_event_get_root (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_x:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: x of #IBusXEvent
++ */
++gint ibus_x_event_get_x (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_y:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: y of #IBusXEvent
++ */
++gint ibus_x_event_get_y (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_x_root:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: x-root of #IBusXEvent
++ */
++gint ibus_x_event_get_x_root (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_y_root:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: y-root of #IBusXEvent
++ */
++gint ibus_x_event_get_y_root (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_same_screen:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: same_screen of #IBusXEvent
++ */
++gboolean ibus_x_event_get_same_screen
++ (IBusXEvent *event);
++
++/**
++ * ibus_x_event_get_purpose:
++ * @event: An #IBusXEvent.
++ *
++ * Returns: purpose of #IBusXEvent
++ */
++const gchar * ibus_x_event_get_purpose (IBusXEvent *event);
++
++G_END_DECLS
++#endif
+diff --git a/src/tests/runtest b/src/tests/runtest
+index 91c4e95f..0e43fee5 100755
+--- a/src/tests/runtest
++++ b/src/tests/runtest
+@@ -106,6 +106,7 @@ test -d $tstdir || mkdir $tstdir
+ --daemonize \
+ --cache=none \
+ --panel=disable \
++ --panel-extension=disable \
+ --config=default \
+ --verbose;
+
+diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
+index 786b80e6..0a8f4200 100644
+--- a/ui/gtk3/Makefile.am
++++ b/ui/gtk3/Makefile.am
+@@ -3,8 +3,8 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2015-2017 Takao Fujwiara <takao.fujiwara1@gmail.com>
+-# Copyright (c) 2007-2017 Red Hat, Inc.
++# Copyright (c) 2015-2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2007-2018 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+ # modify it under the terms of the GNU Lesser General Public
+@@ -30,7 +30,7 @@ component_DATA = \
+ $(NULL)
+ componentdir = $(pkgdatadir)/component
+
+-gtkpanel.xml: gtkpanel.xml.in
++%.xml: %.xml.in
+ $(AM_V_GEN) sed \
+ -e 's|@VERSION[@]|$(VERSION)|g' \
+ -e 's|@libexecdir[@]|$(libexecdir)|g' $< > $@.tmp && \
+@@ -108,6 +108,7 @@ libexec_PROGRAMS = ibus-ui-gtk3
+
+ ibus_ui_gtk3_SOURCES = \
+ application.vala \
++ bindingcommon.vala \
+ candidatearea.vala \
+ candidatepanel.vala \
+ emojier.vala \
+@@ -155,9 +156,12 @@ EXTRA_DIST = \
+ $(emoji_headers) \
+ $(man_seven_in_files) \
+ emojierapp.vala \
++ extension.vala \
++ gtkextension.xml.in \
+ gtkpanel.xml.in \
+ notification-item.xml \
+ notification-watcher.xml \
++ panelbinding.vala \
+ $(NULL)
+
+ if ENABLE_EMOJI_DICT
+@@ -167,10 +171,8 @@ libexec_PROGRAMS += ibus-ui-emojier
+
+ ibus_ui_emojier_VALASOURCES = \
+ emojierapp.vala \
+- candidatearea.vala \
+ emojier.vala \
+ iconwidget.vala \
+- pango.vala \
+ separator.vala \
+ $(NULL)
+ ibus_ui_emojier_SOURCES = \
+@@ -198,6 +200,53 @@ emojierapp.o: $(srcdir)/emojierapp.c
+ $(AM_V_CC_no)$(COMPILE) -c -o $@ $<
+ $(NULL)
+
++component_DATA += gtkextension.xml
++CLEANFILES += gtkextension.xml
++libexec_PROGRAMS += ibus-extension-gtk3
++
++ibus_extension_gtk3_VALASOURCES = \
++ bindingcommon.vala \
++ emojier.vala \
++ extension.vala \
++ iconwidget.vala \
++ keybindingmanager.vala \
++ panelbinding.vala \
++ $(NULL)
++ibus_extension_gtk3_SOURCES = \
++ $(ibus_extension_gtk3_VALASOURCES:.vala=.c) \
++ $(NULL)
++
++ibus_extension_gtk3_LDADD = \
++ $(AM_LDADD) \
++ $(NULL)
++ibus_extension_gtk3_VALAFLAGS = \
++ $(AM_VALAFLAGS) \
++ $(NULL)
++
++# This line and foo_VALASOURCES line can delete the duplicated entries
++# of emojier.c: emojier.vala
++extension.c: $(ibus_extension_gtk3_VALASOURCES)
++ $(AM_V_VALAC)$(am__cd) $(srcdir) && $(VALAC) $(AM_VALAFLAGS) \
++$(VALAFLAGS) -C $(ibus_extension_gtk3_VALASOURCES)
++ $(NULL)
++# make dist creates .c files in a different srcdir
++extension.o: $(srcdir)/extension.c
++ $(AM_V_CC)source='$<' object='$@' libtool=no \
++ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \
++ $(AM_V_CC_no)$(COMPILE) -c -o $@ $<
++ $(NULL)
++# of emojier.c: emojier.vala
++panelbinding.c: $(ibus_extension_gtk3_VALASOURCES)
++ $(AM_V_VALAC)$(am__cd) $(srcdir) && $(VALAC) $(AM_VALAFLAGS) \
++$(VALAFLAGS) -C $(ibus_extension_gtk3_VALASOURCES)
++ $(NULL)
++# make dist creates .c files in a different srcdir
++panelbinding.o: $(srcdir)/panelbinding.c
++ $(AM_V_CC)source='$<' object='$@' libtool=no \
++ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) \
++ $(AM_V_CC_no)$(COMPILE) -c -o $@ $<
++ $(NULL)
++
+ man_seven_files = $(man_seven_in_files:.7.in=.7)
+ man_seven_DATA =$(man_seven_files:.7=.7.gz)
+ man_sevendir = $(mandir)/man7
+diff --git a/ui/gtk3/bindingcommon.vala b/ui/gtk3/bindingcommon.vala
+new file mode 100644
+index 00000000..4171f29d
+--- /dev/null
++++ b/ui/gtk3/bindingcommon.vala
+@@ -0,0 +1,215 @@
++/* vim:set et sts=4 sw=4:
++ *
++ * ibus - The Input Bus
++ *
++ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
++ * Copyright(c) 2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA
++ */
++
++/* This file depends on keybindingmanager.vala */
++
++class BindingCommon {
++ public enum KeyEventFuncType {
++ ANY,
++ IME_SWITCHER,
++ EMOJI_TYPING,
++ }
++
++ public class Keybinding : GLib.Object {
++ public Keybinding(uint keysym,
++ Gdk.ModifierType modifiers,
++ bool reverse,
++ KeyEventFuncType ftype) {
++ this.keysym = keysym;
++ this.modifiers = modifiers;
++ this.reverse = reverse;
++ this.ftype = ftype;
++ }
++
++ public uint keysym { get; set; }
++ public Gdk.ModifierType modifiers { get; set; }
++ public bool reverse { get; set; }
++ public KeyEventFuncType ftype { get; set; }
++ }
++
++ public delegate void KeybindingFuncHandlerFunc(Gdk.Event event);
++
++ public static void
++ keybinding_manager_bind(KeybindingManager keybinding_manager,
++ ref GLib.List<Keybinding> keybindings,
++ string? accelerator,
++ KeyEventFuncType ftype,
++ KeybindingManager.KeybindingHandlerFunc
++ handler_normal,
++ KeybindingManager.KeybindingHandlerFunc?
++ handler_reverse) {
++ uint switch_keysym = 0;
++ Gdk.ModifierType switch_modifiers = 0;
++ Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
++ Keybinding keybinding;
++
++ Gtk.accelerator_parse(accelerator,
++ out switch_keysym, out switch_modifiers);
++
++ // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
++ const Gdk.ModifierType VIRTUAL_MODIFIERS = (
++ Gdk.ModifierType.SUPER_MASK |
++ Gdk.ModifierType.HYPER_MASK |
++ Gdk.ModifierType.META_MASK);
++ if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
++ // workaround a bug in gdk vapi vala > 0.18
++ // https://bugzilla.gnome.org/show_bug.cgi?id=677559
++#if VALA_0_18
++ Gdk.Keymap.get_default().map_virtual_modifiers(
++ ref switch_modifiers);
++#else
++ if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
++ switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
++ if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
++ switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
++#endif
++ switch_modifiers &= ~VIRTUAL_MODIFIERS;
++ }
++
++ if (switch_keysym == 0 && switch_modifiers == 0) {
++ warning("Parse accelerator '%s' failed!", accelerator);
++ return;
++ }
++
++ keybinding = new Keybinding(switch_keysym,
++ switch_modifiers,
++ false,
++ ftype);
++ keybindings.append(keybinding);
++
++ keybinding_manager.bind(switch_keysym, switch_modifiers,
++ handler_normal);
++ if (ftype == KeyEventFuncType.EMOJI_TYPING) {
++ return;
++ }
++
++ // accelerator already has Shift mask
++ if ((switch_modifiers & reverse_modifier) != 0) {
++ return;
++ }
++
++ switch_modifiers |= reverse_modifier;
++
++ keybinding = new Keybinding(switch_keysym,
++ switch_modifiers,
++ true,
++ ftype);
++ keybindings.append(keybinding);
++
++ if (ftype == KeyEventFuncType.IME_SWITCHER) {
++ keybinding_manager.bind(switch_keysym, switch_modifiers,
++ handler_reverse);
++ }
++ return;
++ }
++
++ public static void
++ unbind_switch_shortcut(KeyEventFuncType ftype,
++ GLib.List<Keybinding> keybindings) {
++ var keybinding_manager = KeybindingManager.get_instance();
++
++ while (keybindings != null) {
++ Keybinding keybinding = keybindings.data;
++
++ if (ftype == KeyEventFuncType.ANY ||
++ ftype == keybinding.ftype) {
++ keybinding_manager.unbind(keybinding.keysym,
++ keybinding.modifiers);
++ }
++ keybindings = keybindings.next;
++ }
++ }
++
++ public static void
++ set_custom_font(GLib.Settings? settings_panel,
++ GLib.Settings? settings_emoji,
++ ref Gtk.CssProvider? css_provider) {
++ Gdk.Display display = Gdk.Display.get_default();
++ Gdk.Screen screen = (display != null) ?
++ display.get_default_screen() : null;
++
++ if (screen == null) {
++ warning("Could not open display.");
++ return;
++ }
++
++ if (settings_emoji != null) {
++ string emoji_font = settings_emoji.get_string("font");
++ if (emoji_font == null) {
++ warning("No config emoji:font.");
++ return;
++ }
++ IBusEmojier.set_emoji_font(emoji_font);
++ }
++
++ if (settings_panel == null)
++ return;
++
++ bool use_custom_font = settings_panel.get_boolean("use-custom-font");
++
++ if (css_provider != null) {
++ Gtk.StyleContext.remove_provider_for_screen(screen,
++ css_provider);
++ css_provider = null;
++ }
++
++ if (use_custom_font == false) {
++ return;
++ }
++
++ string custom_font = settings_panel.get_string("custom-font");
++ if (custom_font == null) {
++ warning("No config panel:custom-font.");
++ return;
++ }
++
++ Pango.FontDescription font_desc =
++ Pango.FontDescription.from_string(custom_font);
++ string font_family = font_desc.get_family();
++ int font_size = font_desc.get_size() / Pango.SCALE;
++ string data;
++
++ if (Gtk.MAJOR_VERSION < 3 ||
++ (Gtk.MAJOR_VERSION == 3 && Gtk.MINOR_VERSION < 20)) {
++ data = "GtkLabel { font: %s; }".printf(custom_font);
++ } else {
++ data = "label { font-family: %s; font-size: %dpt; }"
++ .printf(font_family, font_size);
++ }
++
++ css_provider = new Gtk.CssProvider();
++
++ try {
++ css_provider.load_from_data(data, -1);
++ } catch (GLib.Error e) {
++ warning("Failed css_provider_from_data: %s: %s", custom_font,
++ e.message);
++ return;
++ }
++
++ Gtk.StyleContext.add_provider_for_screen(
++ screen,
++ css_provider,
++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++ }
++}
+diff --git a/ui/gtk3/candidatearea.vala b/ui/gtk3/candidatearea.vala
+index e162a960..f590cf3a 100644
+--- a/ui/gtk3/candidatearea.vala
++++ b/ui/gtk3/candidatearea.vala
+@@ -21,108 +21,6 @@
+ * USA
+ */
+
+-class ThemedRGBA {
+- public Gdk.RGBA *normal_fg { get; set; }
+- public Gdk.RGBA *normal_bg { get; set; }
+- public Gdk.RGBA *selected_fg { get; set; }
+- public Gdk.RGBA *selected_bg { get; set; }
+-
+- private Gtk.StyleContext m_style_context;
+-
+- public ThemedRGBA(Gtk.Widget widget) {
+- this.normal_fg = null;
+- this.normal_bg = null;
+- this.selected_fg = null;
+- this.selected_bg = null;
+-
+- /* Use the color of Gtk.TextView instead of Gtk.Label
+- * because the selected label "color" is not configured
+- * in "Adwaita" theme and the selected label "background-color"
+- * is not configured in "Maia" theme.
+- * https://github.com/ibus/ibus/issues/1871
+- */
+- Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
+- widget_path.append_type(typeof(Gtk.TextView));
+- m_style_context = new Gtk.StyleContext();
+- m_style_context.set_path(widget_path);
+- m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
+-
+- /* "-gtk-secondary-caret-color" value is different
+- * if the parent widget is set in "Menta" theme.
+- */
+- m_style_context.set_parent(widget.get_style_context());
+-
+- get_rgba();
+-
+- m_style_context.changed.connect(() => { get_rgba(); });
+- }
+-
+- ~ThemedRGBA() {
+- reset_rgba();
+- }
+-
+- private void reset_rgba() {
+- if (this.normal_fg != null) {
+- this.normal_fg.free();
+- this.normal_fg = null;
+- }
+- if (this.normal_bg != null) {
+- this.normal_bg.free();
+- this.normal_bg = null;
+- }
+- if (this.selected_fg != null) {
+- this.selected_fg.free();
+- this.selected_fg = null;
+- }
+- if (this.selected_bg != null) {
+- this.selected_bg.free();
+- this.selected_bg = null;
+- }
+- }
+-
+- private void get_rgba() {
+- reset_rgba();
+- Gdk.RGBA *normal_fg = null;
+- Gdk.RGBA *normal_bg = null;
+- Gdk.RGBA *selected_fg = null;
+- Gdk.RGBA *selected_bg = null;
+- m_style_context.get(Gtk.StateFlags.NORMAL,
+- "color",
+- out normal_fg);
+- m_style_context.get(Gtk.StateFlags.SELECTED,
+- "color",
+- out selected_fg);
+-
+- string bg_prop = "background-color";
+- m_style_context.get(Gtk.StateFlags.NORMAL,
+- bg_prop,
+- out normal_bg);
+- m_style_context.get(Gtk.StateFlags.SELECTED,
+- bg_prop,
+- out selected_bg);
+- if (normal_bg.red == selected_bg.red &&
+- normal_bg.green == selected_bg.green &&
+- normal_bg.blue == selected_bg.blue &&
+- normal_bg.alpha == selected_bg.alpha) {
+- normal_bg.free();
+- normal_bg = null;
+- normal_bg.free();
+- normal_bg = null;
+- bg_prop = "-gtk-secondary-caret-color";
+- m_style_context.get(Gtk.StateFlags.NORMAL,
+- bg_prop,
+- out normal_bg);
+- m_style_context.get(Gtk.StateFlags.SELECTED,
+- bg_prop,
+- out selected_bg);
+- }
+- this.normal_fg = normal_fg;
+- this.normal_bg = normal_bg;
+- this.selected_fg = selected_fg;
+- this.selected_bg = selected_bg;
+- }
+-}
+-
+ class CandidateArea : Gtk.Box {
+ private bool m_vertical;
+ private Gtk.Label[] m_labels;
+diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala
+new file mode 100644
+index 00000000..a170280b
+--- /dev/null
++++ b/ui/gtk3/extension.vala
+@@ -0,0 +1,124 @@
++/* vim:set et sts=4 sw=4:
++ *
++ * ibus - The Input Bus
++ *
++ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
++ * Copyright(c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA
++ */
++
++class ExtensionGtk {
++ private IBus.Bus m_bus;
++ private PanelBinding m_panel;
++
++ public ExtensionGtk(string[] argv) {
++ GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE,
++ Config.GLIB_LOCALE_DIR);
++ GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
++ IBus.init();
++ Gtk.init(ref argv);
++
++ m_bus = new IBus.Bus();
++
++ m_bus.connected.connect(bus_connected);
++ m_bus.disconnected.connect(bus_disconnected);
++
++ if (m_bus.is_connected()) {
++ init();
++ }
++ }
++
++ private void init() {
++ DBusConnection connection = m_bus.get_connection();
++ connection.signal_subscribe("org.freedesktop.DBus",
++ "org.freedesktop.DBus",
++ "NameAcquired",
++ "/org/freedesktop/DBus",
++ IBus.SERVICE_PANEL_EXTENSION,
++ DBusSignalFlags.NONE,
++ bus_name_acquired_cb);
++ connection.signal_subscribe("org.freedesktop.DBus",
++ "org.freedesktop.DBus",
++ "NameLost",
++ "/org/freedesktop/DBus",
++ IBus.SERVICE_PANEL_EXTENSION,
++ DBusSignalFlags.NONE,
++ bus_name_lost_cb);
++ var flags =
++ IBus.BusNameFlag.ALLOW_REPLACEMENT |
++ IBus.BusNameFlag.REPLACE_EXISTING;
++ m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION, flags);
++ }
++
++ public int run() {
++ Gtk.main();
++ return 0;
++ }
++
++ private void bus_name_acquired_cb(DBusConnection connection,
++ string sender_name,
++ string object_path,
++ string interface_name,
++ string signal_name,
++ Variant parameters) {
++ debug("signal_name = %s", signal_name);
++ m_panel = new PanelBinding(m_bus);
++ m_panel.load_settings();
++ }
++
++ private void bus_name_lost_cb(DBusConnection connection,
++ string sender_name,
++ string object_path,
++ string interface_name,
++ string signal_name,
++ Variant parameters) {
++ // "Destroy" dbus method was called before this callback is called.
++ // "Destroy" dbus method -> ibus_service_destroy()
++ // -> g_dbus_connection_unregister_object()
++ // -> g_object_unref(m_panel) will be called later with an idle method,
++ // which was assigned in the arguments of
++ // g_dbus_connection_register_object()
++ debug("signal_name = %s", signal_name);
++
++ // unref m_panel
++ m_panel.disconnect_signals();
++ m_panel = null;
++ }
++
++ private void bus_disconnected(IBus.Bus bus) {
++ debug("connection is lost.");
++ Gtk.main_quit();
++ }
++
++ private void bus_connected(IBus.Bus bus) {
++ init();
++ }
++
++ public static void main(string[] argv) {
++ // https://bugzilla.redhat.com/show_bug.cgi?id=1226465#c20
++ // In /etc/xdg/plasma-workspace/env/gtk3_scrolling.sh
++ // Plasma deskop sets this variable and prevents Super-space,
++ // and Ctrl-Shift-e when ibus-ui-gtk3 runs after the
++ // desktop is launched.
++ GLib.Environment.unset_variable("GDK_CORE_DEVICE_EVENTS");
++ // for Gdk.X11.get_default_xdisplay()
++ Gdk.set_allowed_backends("x11");
++
++ ExtensionGtk extension = new ExtensionGtk(argv);
++ extension.run();
++ }
++}
+diff --git a/ui/gtk3/gtkextension.xml.in b/ui/gtk3/gtkextension.xml.in
+new file mode 100644
+index 00000000..b8157c97
+--- /dev/null
++++ b/ui/gtk3/gtkextension.xml.in
+@@ -0,0 +1,12 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!-- filename: gtkextension.xml -->
++<component>
++ <name>org.freedesktop.IBus.Panel.Extension</name>
++ <description>Gtk Panel Extension Component</description>
++ <exec>@libexecdir@/ibus-extension-gtk3</exec>
++ <version>@VERSION@</version>
++ <author>Takao Fujiwara <takao.fujiwara1@gmail.com></author>
++ <license>GPL</license>
++ <homepage>https://github.com/ibus/ibus/wiki</homepage>
++ <textdomain>ibus</textdomain>
++</component>
+diff --git a/ui/gtk3/iconwidget.vala b/ui/gtk3/iconwidget.vala
+index d322650c..36643c74 100644
+--- a/ui/gtk3/iconwidget.vala
++++ b/ui/gtk3/iconwidget.vala
+@@ -3,6 +3,7 @@
+ * ibus - The Input Bus
+ *
+ * Copyright(c) 2011-2014 Peng Huang <shawn.p.huang@gmail.com>
++ * Copyright(c) 2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+@@ -20,6 +21,108 @@
+ * USA
+ */
+
++class ThemedRGBA {
++ public Gdk.RGBA *normal_fg { get; set; }
++ public Gdk.RGBA *normal_bg { get; set; }
++ public Gdk.RGBA *selected_fg { get; set; }
++ public Gdk.RGBA *selected_bg { get; set; }
++
++ private Gtk.StyleContext m_style_context;
++
++ public ThemedRGBA(Gtk.Widget widget) {
++ this.normal_fg = null;
++ this.normal_bg = null;
++ this.selected_fg = null;
++ this.selected_bg = null;
++
++ /* Use the color of Gtk.TextView instead of Gtk.Label
++ * because the selected label "color" is not configured
++ * in "Adwaita" theme and the selected label "background-color"
++ * is not configured in "Maia" theme.
++ * https://github.com/ibus/ibus/issues/1871
++ */
++ Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
++ widget_path.append_type(typeof(Gtk.TextView));
++ m_style_context = new Gtk.StyleContext();
++ m_style_context.set_path(widget_path);
++ m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
++
++ /* "-gtk-secondary-caret-color" value is different
++ * if the parent widget is set in "Menta" theme.
++ */
++ m_style_context.set_parent(widget.get_style_context());
++
++ get_rgba();
++
++ m_style_context.changed.connect(() => { get_rgba(); });
++ }
++
++ ~ThemedRGBA() {
++ reset_rgba();
++ }
++
++ private void reset_rgba() {
++ if (this.normal_fg != null) {
++ this.normal_fg.free();
++ this.normal_fg = null;
++ }
++ if (this.normal_bg != null) {
++ this.normal_bg.free();
++ this.normal_bg = null;
++ }
++ if (this.selected_fg != null) {
++ this.selected_fg.free();
++ this.selected_fg = null;
++ }
++ if (this.selected_bg != null) {
++ this.selected_bg.free();
++ this.selected_bg = null;
++ }
++ }
++
++ private void get_rgba() {
++ reset_rgba();
++ Gdk.RGBA *normal_fg = null;
++ Gdk.RGBA *normal_bg = null;
++ Gdk.RGBA *selected_fg = null;
++ Gdk.RGBA *selected_bg = null;
++ m_style_context.get(Gtk.StateFlags.NORMAL,
++ "color",
++ out normal_fg);
++ m_style_context.get(Gtk.StateFlags.SELECTED,
++ "color",
++ out selected_fg);
++
++ string bg_prop = "background-color";
++ m_style_context.get(Gtk.StateFlags.NORMAL,
++ bg_prop,
++ out normal_bg);
++ m_style_context.get(Gtk.StateFlags.SELECTED,
++ bg_prop,
++ out selected_bg);
++ if (normal_bg.red == selected_bg.red &&
++ normal_bg.green == selected_bg.green &&
++ normal_bg.blue == selected_bg.blue &&
++ normal_bg.alpha == selected_bg.alpha) {
++ normal_bg.free();
++ normal_bg = null;
++ normal_bg.free();
++ normal_bg = null;
++ bg_prop = "-gtk-secondary-caret-color";
++ m_style_context.get(Gtk.StateFlags.NORMAL,
++ bg_prop,
++ out normal_bg);
++ m_style_context.get(Gtk.StateFlags.SELECTED,
++ bg_prop,
++ out selected_bg);
++ }
++ this.normal_fg = normal_fg;
++ this.normal_bg = normal_bg;
++ this.selected_fg = selected_fg;
++ this.selected_bg = selected_bg;
++ }
++}
++
+ class IconWidget: Gtk.Image {
+ /**
+ * IconWidget:
+diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
+index bcb3ed75..d9238c89 100644
+--- a/ui/gtk3/panel.vala
++++ b/ui/gtk3/panel.vala
+@@ -22,39 +22,16 @@
+ */
+
+ class Panel : IBus.PanelService {
+- private class Keybinding {
+- public Keybinding(uint keysym,
+- Gdk.ModifierType modifiers,
+- bool reverse,
+- KeyEventFuncType ftype) {
+- this.keysym = keysym;
+- this.modifiers = modifiers;
+- this.reverse = reverse;
+- this.ftype = ftype;
+- }
+-
+- public uint keysym { get; set; }
+- public Gdk.ModifierType modifiers { get; set; }
+- public bool reverse { get; set; }
+- public KeyEventFuncType ftype { get; set; }
+- }
+
+ private enum IconType {
+ STATUS_ICON,
+ INDICATOR,
+ }
+
+- private enum KeyEventFuncType {
+- ANY,
+- IME_SWITCHER,
+- EMOJI_TYPING,
+- }
+-
+ private IBus.Bus m_bus;
+ private GLib.Settings m_settings_general = null;
+ private GLib.Settings m_settings_hotkey = null;
+ private GLib.Settings m_settings_panel = null;
+- private GLib.Settings m_settings_emoji = null;
+ private IconType m_icon_type = IconType.STATUS_ICON;
+ private Indicator m_indicator;
+ #if INDICATOR
+@@ -73,10 +50,6 @@ class Panel : IBus.PanelService {
+ private CandidatePanel m_candidate_panel;
+ private Switcher m_switcher;
+ private uint m_switcher_focus_set_engine_id;
+- private IBusEmojier? m_emojier;
+- private uint m_emojier_set_emoji_lang_id;
+- private uint m_emojier_focus_commit_text_id;
+- private string[] m_emojier_favorites = {};
+ private PropertyManager m_property_manager;
+ private PropertyPanel m_property_panel;
+ private GLib.Pid m_setup_pid = 0;
+@@ -108,7 +81,8 @@ class Panel : IBus.PanelService {
+ private ulong m_activate_id;
+ private ulong m_registered_status_notifier_item_id;
+
+- private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
++ private GLib.List<BindingCommon.Keybinding> m_keybindings =
++ new GLib.List<BindingCommon.Keybinding>();
+
+ public Panel(IBus.Bus bus) {
+ GLib.assert(bus.is_connected());
+@@ -147,8 +121,6 @@ class Panel : IBus.PanelService {
+ m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
+ }
+
+- bind_emoji_shortcut();
+-
+ m_property_manager = new PropertyManager();
+ m_property_manager.property_activate.connect((w, k, s) => {
+ property_activate(k, s);
+@@ -168,7 +140,9 @@ class Panel : IBus.PanelService {
+ if (m_indicator != null)
+ m_indicator.unregister_connection();
+ #endif
+- unbind_switch_shortcut(KeyEventFuncType.ANY);
++ BindingCommon.unbind_switch_shortcut(
++ BindingCommon.KeyEventFuncType.ANY, m_keybindings);
++ m_keybindings = null;
+ }
+
+ private void init_settings() {
+@@ -176,7 +150,6 @@ class Panel : IBus.PanelService {
+ m_settings_hotkey =
+ new GLib.Settings("org.freedesktop.ibus.general.hotkey");
+ m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
+- m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji");
+
+ m_settings_general.changed["preload-engines"].connect((key) => {
+ update_engines(m_settings_general.get_strv(key),
+@@ -205,16 +178,23 @@ class Panel : IBus.PanelService {
+ });
+
+ m_settings_hotkey.changed["triggers"].connect((key) => {
+- unbind_switch_shortcut(KeyEventFuncType.IME_SWITCHER);
++ BindingCommon.unbind_switch_shortcut(
++ BindingCommon.KeyEventFuncType.IME_SWITCHER,
++ m_keybindings);
++ m_keybindings = null;
+ bind_switch_shortcut();
+ });
+
+ m_settings_panel.changed["custom-font"].connect((key) => {
+- set_custom_font();
++ BindingCommon.set_custom_font(m_settings_panel,
++ null,
++ ref m_css_provider);
+ });
+
+ m_settings_panel.changed["use-custom-font"].connect((key) => {
+- set_custom_font();
++ BindingCommon.set_custom_font(m_settings_panel,
++ null,
++ ref m_css_provider);
+ });
+
+ m_settings_panel.changed["show-icon-on-systray"].connect((key) => {
+@@ -245,39 +225,6 @@ class Panel : IBus.PanelService {
+ m_settings_panel.changed["property-icon-delay-time"].connect((key) => {
+ set_property_icon_delay_time();
+ });
+-
+- m_settings_emoji.changed["hotkey"].connect((key) => {
+- unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING);
+- bind_emoji_shortcut();
+- });
+-
+- m_settings_emoji.changed["font"].connect((key) => {
+- set_custom_font();
+- });
+-
+- m_settings_emoji.changed["favorites"].connect((key) => {
+- set_emoji_favorites();
+- });
+-
+- m_settings_emoji.changed["favorite-annotations"].connect((key) => {
+- set_emoji_favorites();
+- });
+-
+- m_settings_emoji.changed["lang"].connect((key) => {
+- set_emoji_lang();
+- });
+-
+- m_settings_emoji.changed["has-partial-match"].connect((key) => {
+- set_emoji_partial_match();
+- });
+-
+- m_settings_emoji.changed["partial-match-length"].connect((key) => {
+- set_emoji_partial_match();
+- });
+-
+- m_settings_emoji.changed["partial-match-condition"].connect((key) => {
+- set_emoji_partial_match();
+- });
+ }
+
+ private void popup_menu_at_area_window(Gtk.Menu menu,
+@@ -409,120 +356,40 @@ class Panel : IBus.PanelService {
+ m_status_icon.set_from_icon_name("ibus-keyboard");
+ }
+
+- private void keybinding_manager_bind(KeybindingManager keybinding_manager,
+- string? accelerator,
+- KeyEventFuncType ftype) {
+- uint switch_keysym = 0;
+- Gdk.ModifierType switch_modifiers = 0;
+- Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
+- Keybinding keybinding;
+-
+- Gtk.accelerator_parse(accelerator,
+- out switch_keysym, out switch_modifiers);
+-
+- // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
+- const Gdk.ModifierType VIRTUAL_MODIFIERS = (
+- Gdk.ModifierType.SUPER_MASK |
+- Gdk.ModifierType.HYPER_MASK |
+- Gdk.ModifierType.META_MASK);
+- if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
+- // workaround a bug in gdk vapi vala > 0.18
+- // https://bugzilla.gnome.org/show_bug.cgi?id=677559
+-#if VALA_0_18
+- Gdk.Keymap.get_default().map_virtual_modifiers(
+- ref switch_modifiers);
+-#else
+- if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
+- switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+- if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
+- switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+-#endif
+- switch_modifiers &= ~VIRTUAL_MODIFIERS;
+- }
+-
+- if (switch_keysym == 0 && switch_modifiers == 0) {
+- warning("Parse accelerator '%s' failed!", accelerator);
+- return;
+- }
+-
+- keybinding = new Keybinding(switch_keysym,
+- switch_modifiers,
+- false,
+- ftype);
+- m_keybindings.append(keybinding);
+-
+- /* Workaround not to free the pointer of handle_engine_switch() */
+- if (ftype == KeyEventFuncType.IME_SWITCHER) {
+- keybinding_manager.bind(switch_keysym, switch_modifiers,
+- (e) => handle_engine_switch(e, false));
+- } else if (ftype == KeyEventFuncType.EMOJI_TYPING) {
+- keybinding_manager.bind(switch_keysym, switch_modifiers,
+- (e) => handle_emoji_typing(e));
+- return;
+- }
+-
+- // accelerator already has Shift mask
+- if ((switch_modifiers & reverse_modifier) != 0) {
+- return;
+- }
+-
+- switch_modifiers |= reverse_modifier;
+-
+- keybinding = new Keybinding(switch_keysym,
+- switch_modifiers,
+- true,
+- ftype);
+- m_keybindings.append(keybinding);
+-
+- if (ftype == KeyEventFuncType.IME_SWITCHER) {
+- keybinding_manager.bind(switch_keysym, switch_modifiers,
+- (e) => handle_engine_switch(e, true));
+- }
+- }
+-
+ private void bind_switch_shortcut() {
+ string[] accelerators = m_settings_hotkey.get_strv("triggers");
+
+ var keybinding_manager = KeybindingManager.get_instance();
+
+ foreach (var accelerator in accelerators) {
+- keybinding_manager_bind(keybinding_manager,
+- accelerator,
+- KeyEventFuncType.IME_SWITCHER);
+- }
+- }
+-
+- private void bind_emoji_shortcut() {
+-#if EMOJI_DICT
+- string[] accelerators = m_settings_emoji.get_strv("hotkey");
+-
+- var keybinding_manager = KeybindingManager.get_instance();
+-
+- foreach (var accelerator in accelerators) {
+- keybinding_manager_bind(keybinding_manager,
+- accelerator,
+- KeyEventFuncType.EMOJI_TYPING);
++ BindingCommon.keybinding_manager_bind(
++ keybinding_manager,
++ ref m_keybindings,
++ accelerator,
++ BindingCommon.KeyEventFuncType.IME_SWITCHER,
++ handle_engine_switch_normal,
++ handle_engine_switch_reverse);
+ }
+-#endif
+ }
+
+- private void unbind_switch_shortcut(KeyEventFuncType ftype) {
++/*
++ public static void
++ unbind_switch_shortcut(KeyEventFuncType ftype,
++ GLib.List<Keybinding> keybindings) {
+ var keybinding_manager = KeybindingManager.get_instance();
+
+- unowned GLib.List<Keybinding> keybindings = m_keybindings;
+-
+ while (keybindings != null) {
+ Keybinding keybinding = keybindings.data;
+
+- if (ftype == KeyEventFuncType.ANY || ftype == keybinding.ftype) {
++ if (ftype == KeyEventFuncType.ANY ||
++ ftype == keybinding.ftype) {
+ keybinding_manager.unbind(keybinding.keysym,
+ keybinding.modifiers);
+ }
+ keybindings = keybindings.next;
+ }
+-
+- m_keybindings = null;
+ }
++*/
+
+ /**
+ * panel_get_engines_from_xkb:
+@@ -670,69 +537,6 @@ class Panel : IBus.PanelService {
+ m_settings_general.set_strv("preload-engines", names);
+ }
+
+- private void set_custom_font() {
+- Gdk.Display display = Gdk.Display.get_default();
+- Gdk.Screen screen = (display != null) ?
+- display.get_default_screen() : null;
+-
+- if (screen == null) {
+- warning("Could not open display.");
+- return;
+- }
+-
+- string emoji_font = m_settings_emoji.get_string("font");
+- if (emoji_font == null) {
+- warning("No config emoji:font.");
+- return;
+- }
+- IBusEmojier.set_emoji_font(emoji_font);
+-
+- bool use_custom_font = m_settings_panel.get_boolean("use-custom-font");
+-
+- if (m_css_provider != null) {
+- Gtk.StyleContext.remove_provider_for_screen(screen,
+- m_css_provider);
+- m_css_provider = null;
+- }
+-
+- if (use_custom_font == false) {
+- return;
+- }
+-
+- string custom_font = m_settings_panel.get_string("custom-font");
+- if (custom_font == null) {
+- warning("No config panel:custom-font.");
+- return;
+- }
+-
+- Pango.FontDescription font_desc =
+- Pango.FontDescription.from_string(custom_font);
+- string font_family = font_desc.get_family();
+- int font_size = font_desc.get_size() / Pango.SCALE;
+- string data;
+-
+- if (Gtk.MAJOR_VERSION < 3 ||
+- (Gtk.MAJOR_VERSION == 3 && Gtk.MINOR_VERSION < 20)) {
+- data = "GtkLabel { font: %s; }".printf(custom_font);
+- } else {
+- data = "label { font-family: %s; font-size: %dpt; }"
+- .printf(font_family, font_size);
+- }
+-
+- m_css_provider = new Gtk.CssProvider();
+-
+- try {
+- m_css_provider.load_from_data(data, -1);
+- } catch (GLib.Error e) {
+- warning("Failed css_provider_from_data: %s: %s", custom_font,
+- e.message);
+- return;
+- }
+-
+- Gtk.StyleContext.add_provider_for_screen(screen,
+- m_css_provider,
+- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+- }
+
+ private void set_switcher_delay_time() {
+ m_switcher_delay_time =
+@@ -855,35 +659,6 @@ class Panel : IBus.PanelService {
+ m_settings_panel.get_int("property-icon-delay-time");
+ }
+
+- private void set_emoji_favorites() {
+- m_emojier_favorites = m_settings_emoji.get_strv("favorites");
+- IBusEmojier.set_favorites(
+- m_emojier_favorites,
+- m_settings_emoji.get_strv("favorite-annotations"));
+- }
+-
+- private void set_emoji_lang() {
+- if (m_emojier_set_emoji_lang_id > 0) {
+- GLib.Source.remove(m_emojier_set_emoji_lang_id);
+- m_emojier_set_emoji_lang_id = 0;
+- }
+- m_emojier_set_emoji_lang_id = GLib.Idle.add(() => {
+- IBusEmojier.set_annotation_lang(
+- m_settings_emoji.get_string("lang"));
+- m_emojier_set_emoji_lang_id = 0;
+- IBusEmojier.load_unicode_dict();
+- return false;
+- });
+- }
+-
+- private void set_emoji_partial_match() {
+- IBusEmojier.set_partial_match(
+- m_settings_emoji.get_boolean("has-partial-match"));
+- IBusEmojier.set_partial_match_length(
+- m_settings_emoji.get_int("partial-match-length"));
+- IBusEmojier.set_partial_match_condition(
+- m_settings_emoji.get_int("partial-match-condition"));
+- }
+
+ private int compare_versions(string version1, string version2) {
+ string[] version1_list = version1.split(".");
+@@ -985,12 +760,16 @@ class Panel : IBus.PanelService {
+ set_use_xmodmap();
+ update_engines(m_settings_general.get_strv("preload-engines"),
+ m_settings_general.get_strv("engines-order"));
+- unbind_switch_shortcut(KeyEventFuncType.ANY);
++ BindingCommon.unbind_switch_shortcut(
++ BindingCommon.KeyEventFuncType.ANY,
++ m_keybindings);
++ m_keybindings = null;
+ bind_switch_shortcut();
+- bind_emoji_shortcut();
+ set_switcher_delay_time();
+ set_embed_preedit_text();
+- set_custom_font();
++ BindingCommon.set_custom_font(m_settings_panel,
++ null,
++ ref m_css_provider);
+ set_show_icon_on_systray();
+ set_lookup_table_orientation();
+ set_show_property_panel();
+@@ -998,9 +777,6 @@ class Panel : IBus.PanelService {
+ set_follow_input_cursor_when_always_shown_property_panel();
+ set_xkb_icon_rgba();
+ set_property_icon_delay_time();
+- set_emoji_favorites();
+- set_emoji_lang();
+- set_emoji_partial_match();
+ }
+
+ /**
+@@ -1037,10 +813,6 @@ class Panel : IBus.PanelService {
+ GLib.Source.remove(m_preload_engines_id);
+ m_preload_engines_id = 0;
+ }
+- if (m_emojier_set_emoji_lang_id > 0) {
+- GLib.Source.remove(m_emojier_set_emoji_lang_id);
+- m_emojier_set_emoji_lang_id = 0;
+- }
+ }
+
+ private void engine_contexts_insert(IBus.EngineDesc engine) {
+@@ -1091,7 +863,15 @@ class Panel : IBus.PanelService {
+ set_engine(engine);
+ }
+
+- private void handle_engine_switch(Gdk.Event event, bool revert) {
++ private void handle_engine_switch_normal(Gdk.Event event) {
++ handle_engine_switch(event, false);
++ }
++
++ private void handle_engine_switch_reverse(Gdk.Event event) {
++ handle_engine_switch(event, true);
++ }
++
++ private void handle_engine_switch(Gdk.Event event, bool reverse) {
+ // Do not need switch IME
+ if (m_engines.length <= 1)
+ return;
+@@ -1105,12 +885,12 @@ class Panel : IBus.PanelService {
+ bool pressed = KeybindingManager.primary_modifier_still_pressed(
+ event, primary_modifiers);
+
+- if (revert) {
++ if (reverse) {
+ modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
+ }
+
+ if (pressed && m_switcher_delay_time >= 0) {
+- int i = revert ? m_engines.length - 1 : 1;
++ int i = reverse ? m_engines.length - 1 : 1;
+
+ /* The flag of m_switcher.is_running avoids the following problem:
+ *
+@@ -1132,28 +912,11 @@ class Panel : IBus.PanelService {
+ this.switcher_focus_set_engine();
+ }
+ } else {
+- int i = revert ? m_engines.length - 1 : 1;
++ int i = reverse ? m_engines.length - 1 : 1;
+ switch_engine(i);
+ }
+ }
+
+- private void show_emojier(Gdk.Event event) {
+- m_emojier = new IBusEmojier();
+- string emoji = m_emojier.run(m_real_current_context_path, event);
+- if (emoji == null) {
+- m_emojier = null;
+- return;
+- }
+- this.emojier_focus_commit();
+- }
+-
+- private void handle_emoji_typing(Gdk.Event event) {
+- if (m_emojier != null && m_emojier.is_running()) {
+- m_emojier.present_centralize(event);
+- return;
+- }
+- show_emojier(event);
+- }
+
+ private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
+ string[] names = {};
+@@ -1393,7 +1156,18 @@ class Panel : IBus.PanelService {
+ event.key.window = Gdk.get_default_root_window();
+ event.key.window.ref();
+ }
+- handle_emoji_typing(event);
++ IBus.XEvent xevent = new IBus.XEvent(
++ "event-type", IBus.XEventType.KEY_PRESS,
++ "window",
++ (event.key.window as Gdk.X11.Window).get_xid(),
++ "time", event.key.time,
++ "purpose", "emoji");
++ /* new GLib.Variant("(sv)", "emoji", xevent.serialize_object())
++ * will call g_variant_unref() for the child variant by vala.
++ * I have no idea not to unref the object so integrated
++ * the purpose to IBus.XEvent above.
++ */
++ panel_extension(xevent.serialize_object());
+ });
+ m_sys_menu.append(item);
+ #endif
+@@ -1557,67 +1331,6 @@ class Panel : IBus.PanelService {
+ }
+ }
+
+- private bool emojier_focus_commit_real() {
+- if (m_emojier == null)
+- return true;
+- string selected_string = m_emojier.get_selected_string();
+- string prev_context_path = m_emojier.get_input_context_path();
+- if (selected_string != null &&
+- prev_context_path != "" &&
+- prev_context_path == m_current_context_path) {
+- IBus.Text text = new IBus.Text.from_string(selected_string);
+- commit_text(text);
+- m_emojier = null;
+- bool has_favorite = false;
+- foreach (unowned string favorite in m_emojier_favorites) {
+- if (favorite == selected_string) {
+- has_favorite = true;
+- break;
+- }
+- }
+- if (!has_favorite) {
+- m_emojier_favorites += selected_string;
+- m_settings_emoji.set_strv("favorites", m_emojier_favorites);
+- }
+- return true;
+- }
+-
+- return false;
+- }
+-
+- private void emojier_focus_commit() {
+- if (m_emojier == null)
+- return;
+- string selected_string = m_emojier.get_selected_string();
+- string prev_context_path = m_emojier.get_input_context_path();
+- if (selected_string == null &&
+- prev_context_path != "" &&
+- m_emojier.is_running()) {
+- var context = GLib.MainContext.default();
+- if (m_emojier_focus_commit_text_id > 0 &&
+- context.find_source_by_id(m_emojier_focus_commit_text_id)
+- != null) {
+- GLib.Source.remove(m_emojier_focus_commit_text_id);
+- }
+- m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => {
+- // focus_in is comming before switcher returns
+- emojier_focus_commit_real();
+- m_emojier_focus_commit_text_id = -1;
+- return false;
+- });
+- } else {
+- if (emojier_focus_commit_real()) {
+- var context = GLib.MainContext.default();
+- if (m_emojier_focus_commit_text_id > 0 &&
+- context.find_source_by_id(m_emojier_focus_commit_text_id)
+- != null) {
+- GLib.Source.remove(m_emojier_focus_commit_text_id);
+- }
+- m_emojier_focus_commit_text_id = -1;
+- }
+- }
+- }
+-
+ public override void focus_in(string input_context_path) {
+ m_current_context_path = input_context_path;
+
+@@ -1632,7 +1345,6 @@ class Panel : IBus.PanelService {
+ m_real_current_context_path = m_current_context_path;
+ m_property_panel.focus_in();
+ this.switcher_focus_set_engine();
+- this.emojier_focus_commit();
+ }
+
+ if (m_use_global_engine)
+diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
+new file mode 100644
+index 00000000..50700121
+--- /dev/null
++++ b/ui/gtk3/panelbinding.vala
+@@ -0,0 +1,335 @@
++/* vim:set et sts=4 sw=4:
++ *
++ * ibus - The Input Bus
++ *
++ * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
++ * Copyright(c) 2018 Takao Fujwiara <takao.fujiwara1@gmail.com>
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA
++ */
++
++class PanelBinding : IBus.PanelService {
++ private IBus.Bus m_bus;
++ private GLib.Settings m_settings_panel = null;
++ private GLib.Settings m_settings_emoji = null;
++ private string m_current_context_path = "";
++ private string m_real_current_context_path = "";
++ private IBusEmojier? m_emojier;
++ private uint m_emojier_set_emoji_lang_id;
++ private uint m_emojier_focus_commit_text_id;
++ private string[] m_emojier_favorites = {};
++ private Gtk.CssProvider m_css_provider;
++ private const uint PRELOAD_ENGINES_DELAY_TIME = 30000;
++ private GLib.List<BindingCommon.Keybinding> m_keybindings =
++ new GLib.List<BindingCommon.Keybinding>();
++
++ public PanelBinding(IBus.Bus bus) {
++ GLib.assert(bus.is_connected());
++ // Chain up base class constructor
++ GLib.Object(connection : bus.get_connection(),
++ object_path : IBus.PATH_PANEL_EXTENSION);
++
++ m_bus = bus;
++
++ init_settings();
++
++ bind_emoji_shortcut();
++ }
++
++
++ ~PanelBinding() {
++ BindingCommon.unbind_switch_shortcut(
++ BindingCommon.KeyEventFuncType.ANY,
++ m_keybindings);
++ }
++
++
++ private void init_settings() {
++ m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
++ m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji");
++
++ m_settings_panel.changed["custom-font"].connect((key) => {
++ BindingCommon.set_custom_font(m_settings_panel,
++ m_settings_emoji,
++ ref m_css_provider);
++ });
++
++ m_settings_panel.changed["use-custom-font"].connect((key) => {
++ BindingCommon.set_custom_font(m_settings_panel,
++ m_settings_emoji,
++ ref m_css_provider);
++ });
++
++ m_settings_emoji.changed["hotkey"].connect((key) => {
++ BindingCommon.unbind_switch_shortcut(
++ BindingCommon.KeyEventFuncType.EMOJI_TYPING,
++ m_keybindings);
++ bind_emoji_shortcut();
++ });
++
++ m_settings_emoji.changed["font"].connect((key) => {
++ BindingCommon.set_custom_font(m_settings_panel,
++ m_settings_emoji,
++ ref m_css_provider);
++ });
++
++ m_settings_emoji.changed["favorites"].connect((key) => {
++ set_emoji_favorites();
++ });
++
++ m_settings_emoji.changed["favorite-annotations"].connect((key) => {
++ set_emoji_favorites();
++ });
++
++ m_settings_emoji.changed["lang"].connect((key) => {
++ set_emoji_lang();
++ });
++
++ m_settings_emoji.changed["has-partial-match"].connect((key) => {
++ set_emoji_partial_match();
++ });
++
++ m_settings_emoji.changed["partial-match-length"].connect((key) => {
++ set_emoji_partial_match();
++ });
++
++ m_settings_emoji.changed["partial-match-condition"].connect((key) => {
++ set_emoji_partial_match();
++ });
++ }
++
++
++ private void bind_emoji_shortcut() {
++#if EMOJI_DICT
++ string[] accelerators = m_settings_emoji.get_strv("hotkey");
++
++ var keybinding_manager = KeybindingManager.get_instance();
++
++ foreach (var accelerator in accelerators) {
++ BindingCommon.keybinding_manager_bind(
++ keybinding_manager,
++ ref m_keybindings,
++ accelerator,
++ BindingCommon.KeyEventFuncType.EMOJI_TYPING,
++ handle_emoji_typing,
++ null);
++ }
++#endif
++ }
++
++
++ private void set_emoji_favorites() {
++ m_emojier_favorites = m_settings_emoji.get_strv("favorites");
++ IBusEmojier.set_favorites(
++ m_emojier_favorites,
++ m_settings_emoji.get_strv("favorite-annotations"));
++ }
++
++
++ private void set_emoji_lang() {
++ if (m_emojier_set_emoji_lang_id > 0) {
++ GLib.Source.remove(m_emojier_set_emoji_lang_id);
++ m_emojier_set_emoji_lang_id = 0;
++ }
++ m_emojier_set_emoji_lang_id = GLib.Idle.add(() => {
++ IBusEmojier.set_annotation_lang(
++ m_settings_emoji.get_string("lang"));
++ m_emojier_set_emoji_lang_id = 0;
++ IBusEmojier.load_unicode_dict();
++ return false;
++ });
++ }
++
++
++ private void set_emoji_partial_match() {
++ IBusEmojier.set_partial_match(
++ m_settings_emoji.get_boolean("has-partial-match"));
++ IBusEmojier.set_partial_match_length(
++ m_settings_emoji.get_int("partial-match-length"));
++ IBusEmojier.set_partial_match_condition(
++ m_settings_emoji.get_int("partial-match-condition"));
++ }
++
++
++ public void load_settings() {
++ BindingCommon.unbind_switch_shortcut(BindingCommon.KeyEventFuncType.ANY,
++ m_keybindings);
++ bind_emoji_shortcut();
++ BindingCommon.set_custom_font(m_settings_panel,
++ m_settings_emoji,
++ ref m_css_provider);
++ set_emoji_favorites();
++ set_emoji_lang();
++ set_emoji_partial_match();
++ }
++
++
++ /**
++ * disconnect_signals:
++ *
++ * Call this API before m_panel = null so that the ref_count becomes 0
++ */
++ public void disconnect_signals() {
++ if (m_emojier_set_emoji_lang_id > 0) {
++ GLib.Source.remove(m_emojier_set_emoji_lang_id);
++ m_emojier_set_emoji_lang_id = 0;
++ }
++ }
++
++
++ private void show_emojier(Gdk.Event event) {
++ m_emojier = new IBusEmojier();
++ string emoji = m_emojier.run(m_real_current_context_path, event);
++ if (emoji == null) {
++ m_emojier = null;
++ return;
++ }
++ this.emojier_focus_commit();
++ }
++
++
++ private void handle_emoji_typing(Gdk.Event event) {
++ if (m_emojier != null && m_emojier.is_running()) {
++ m_emojier.present_centralize(event);
++ return;
++ }
++ show_emojier(event);
++ }
++
++
++ private bool emojier_focus_commit_real() {
++ if (m_emojier == null)
++ return true;
++ string selected_string = m_emojier.get_selected_string();
++ string prev_context_path = m_emojier.get_input_context_path();
++ if (selected_string != null &&
++ prev_context_path != "" &&
++ prev_context_path == m_current_context_path) {
++ IBus.Text text = new IBus.Text.from_string(selected_string);
++ commit_text(text);
++ m_emojier = null;
++ bool has_favorite = false;
++ foreach (unowned string favorite in m_emojier_favorites) {
++ if (favorite == selected_string) {
++ has_favorite = true;
++ break;
++ }
++ }
++ if (!has_favorite) {
++ m_emojier_favorites += selected_string;
++ m_settings_emoji.set_strv("favorites", m_emojier_favorites);
++ }
++ return true;
++ }
++
++ return false;
++ }
++
++
++ private void emojier_focus_commit() {
++ if (m_emojier == null)
++ return;
++ string selected_string = m_emojier.get_selected_string();
++ string prev_context_path = m_emojier.get_input_context_path();
++ if (selected_string == null &&
++ prev_context_path != "" &&
++ m_emojier.is_running()) {
++ var context = GLib.MainContext.default();
++ if (m_emojier_focus_commit_text_id > 0 &&
++ context.find_source_by_id(m_emojier_focus_commit_text_id)
++ != null) {
++ GLib.Source.remove(m_emojier_focus_commit_text_id);
++ }
++ m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => {
++ // focus_in is comming before switcher returns
++ emojier_focus_commit_real();
++ m_emojier_focus_commit_text_id = -1;
++ return false;
++ });
++ } else {
++ if (emojier_focus_commit_real()) {
++ var context = GLib.MainContext.default();
++ if (m_emojier_focus_commit_text_id > 0 &&
++ context.find_source_by_id(m_emojier_focus_commit_text_id)
++ != null) {
++ GLib.Source.remove(m_emojier_focus_commit_text_id);
++ }
++ m_emojier_focus_commit_text_id = -1;
++ }
++ }
++ }
++
++
++ public override void focus_in(string input_context_path) {
++ m_current_context_path = input_context_path;
++
++ /* 'fake' input context is named as
++ * '/org/freedesktop/IBus/InputContext_1' and always send in
++ * focus-out events by ibus-daemon for the global engine mode.
++ * Now ibus-daemon assumes to always use the global engine.
++ * But this event should not be used for modal dialogs
++ * such as Switcher.
++ */
++ if (!input_context_path.has_suffix("InputContext_1")) {
++ m_real_current_context_path = m_current_context_path;
++ this.emojier_focus_commit();
++ }
++ }
++
++
++ public override void focus_out(string input_context_path) {
++ m_current_context_path = "";
++ }
++
++
++ public override void panel_extension_received(GLib.Variant data) {
++ IBus.XEvent? xevent = IBus.Serializable.deserialize_object(data)
++ as IBus.XEvent;
++ if (xevent == null) {
++ warning ("Failed to deserialize IBusXEvent");
++ return;
++ }
++ if (xevent.get_purpose() != "emoji") {
++ string format = "The purpose %s is not implemented in PanelExtension";
++ warning (format.printf(xevent.get_purpose()));
++ return;
++ }
++ Gdk.EventType event_type;
++ if (xevent.get_event_type() == IBus.XEventType.KEY_PRESS) {
++ event_type = Gdk.EventType.KEY_PRESS;
++ } else if (xevent.get_event_type() == IBus.XEventType.KEY_RELEASE) {
++ event_type = Gdk.EventType.KEY_RELEASE;
++ } else {
++ warning ("Not supported type %d".printf(xevent.get_event_type()));
++ return;
++ }
++ Gdk.Event event = new Gdk.Event(event_type);
++ event.key.time = xevent.get_time();
++ Gdk.Display? display = Gdk.Display.get_default();
++ X.Window xid = xevent.get_window();
++ Gdk.X11.Window window;
++ window = Gdk.X11.Window.lookup_for_display(
++ display as Gdk.X11.Display, xid);
++ if (window != null) {
++ event.key.window = window;
++ } else {
++ window = new Gdk.X11.Window.foreign_for_display(
++ display as Gdk.X11.Display, xid);
++ event.key.window = window;
++ }
++ handle_emoji_typing(event);
++ }
++}
+--
+2.14.3
+
+From 366963d57d1468914611c71929cc64c83be9affd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Piotr=20Dr=C4=85g?= <piotrdrag@gmail.com>
+Date: Tue, 20 Feb 2018 18:57:32 +0900
+Subject: [PATCH] Fix typos in translatable strings
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BUG=https://github.com/ibus/ibus/pull/1983
+R=Shawn.P.Huang@gmail.com
+
+Review URL: https://codereview.appspot.com/333670043
+
+Patch from Piotr Drąg <piotrdrag@gmail.com>.
+---
+ data/ibus.schemas.in | 6 +++---
+ ui/gtk3/emojierapp.vala | 2 +-
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
+index 278362d7..4523ccc4 100644
+--- a/data/ibus.schemas.in
++++ b/data/ibus.schemas.in
+@@ -64,7 +64,7 @@
+ <default>[ara,bg,cz,dev,gr,gur,in,jp(kana),mal,mkd,ru,ua]</default>
+ <locale name="C">
+ <short>Latin layouts which have no ASCII</short>
+- <long>US layout is appended to the latin layouts. variant can be
++ <long>US layout is appended to the Latin layouts. variant can be
+ omitted.
+ </long>
+ </locale>
+@@ -299,7 +299,7 @@
+ and blue, 3. a RGB color in form 'rgb(r,g,b)' or
+ 4. a RGBA color in form 'rgba(r,g,b,a)' where 'r',
+ 'g', and 'b' are either integers in the range 0 to 255
+- or precentage values in the range 0% to 100%, and
++ or percentage values in the range 0% to 100%, and
+ 'a' is a floating point value in the range 0 to 1
+ of the alpha.</long>
+ </locale>
+@@ -373,7 +373,7 @@
+ <default>Monospace 16</default>
+ <locale name="C">
+ <short>Custom font</short>
+- <long>Custom font name for emoji chracters on emoji dialog</long>
++ <long>Custom font name for emoji characters on emoji dialog</long>
+ </locale>
+ </schema>
+ <schema>
+diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
+index d816352e..efedf344 100644
+--- a/ui/gtk3/emojierapp.vala
++++ b/ui/gtk3/emojierapp.vala
+@@ -94,7 +94,7 @@ public class EmojiApplication : Application {
+ /* TRANSLATORS: "FONT" should be capital and translatable.
+ * It's used for an argument command --font=FONT
+ */
+- N_("\"FONT\" for emoji chracters on emoji dialog"),
++ N_("\"FONT\" for emoji characters on emoji dialog"),
+ N_("FONT") },
+ { "lang", 0, 0, OptionArg.STRING, out annotation_lang,
+ /* TRANSLATORS: "LANG" should be capital and translatable.
+--
+2.14.3
+
+From d1ebb3d77ebfe8f58188261c383d8122e2125fe6 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Wed, 21 Feb 2018 12:12:11 +0900
+Subject: [PATCH] ui/gtk3: Show code points on Unicode name list dialog
+
+The code points are useful since the list has many names.
+
+R=Shawn.P.Huang@gmail.com
+
+Review URL: https://codereview.appspot.com/340770043
+---
+ ui/gtk3/emojier.vala | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
+index 0bf34da8..c85dfa86 100644
+--- a/ui/gtk3/emojier.vala
++++ b/ui/gtk3/emojier.vala
+@@ -198,7 +198,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ private class EPaddedLabelBox : Gtk.Box {
+ public EPaddedLabelBox(string text,
+ Gtk.Align align,
+- TravelDirection direction=TravelDirection.NONE) {
++ TravelDirection direction=TravelDirection.NONE,
++ string? caption=null) {
+ GLib.Object(
+ name : "IBusEmojierPaddedLabelBox",
+ orientation : Gtk.Orientation.HORIZONTAL,
+@@ -218,6 +219,11 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ }
+ EPaddedLabel label = new EPaddedLabel(text, align);
+ pack_start(label, true, true, 0);
++ if (caption != null) {
++ EPaddedLabel label_r = new EPaddedLabel(caption,
++ Gtk.Align.END);
++ pack_end(label_r, true, true, 0);
++ }
+ }
+ }
+ private class ETitleLabelBox : Gtk.HeaderBar {
+@@ -979,9 +985,13 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+ uint n = 0;
+ foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
+ string name = block.get_name();
++ string caption = "U+%08X".printf(block.get_start());
+ EBoxRow row = new EBoxRow(name);
+ EPaddedLabelBox widget =
+- new EPaddedLabelBox(_(name), Gtk.Align.CENTER);
++ new EPaddedLabelBox(_(name),
++ Gtk.Align.CENTER,
++ TravelDirection.NONE,
++ caption);
+ row.add(widget);
+ m_list_box.add(row);
+ if (n++ == m_category_active_index) {
+--
+2.14.3
+
+From fc54b0c051c2eb4a2c1f836b1415def00314cfc1 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Wed, 21 Feb 2018 12:17:31 +0900
+Subject: [PATCH] ui/gtk3: Load Unicode data when open the dialog by
+ default
+
+The emoji data requires about 10MB and the Unicode data requires about 15MB.
+Now the emoji data is loaded at the time of startup and the Unicode data
+is loaded if users open the dialog at the beginning.
+The settings can be customized with gsettings command and the keys
+of 'load-emoji-at-startup' and 'load-unicode-at-startup' in
+'org.freedesktop.ibus.panel.emoji' schema.
+
+Review URL: https://codereview.appspot.com/340780043
+---
+ data/ibus.schemas.in | 32 ++++++++++++++++++++++++++++++++
+ ui/gtk3/panelbinding.vala | 41 +++++++++++++++++++++++++++++++++++++++--
+ 2 files changed, 71 insertions(+), 2 deletions(-)
+
+diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
+index 4523ccc4..3c6b6f69 100644
+--- a/data/ibus.schemas.in
++++ b/data/ibus.schemas.in
+@@ -455,6 +455,38 @@
+ </long>
+ </locale>
+ </schema>
++ <schema>
++ <key>/schemas/desktop/ibus/panel/emoji/load-emoji-at-startup</key>
++ <applyto>/desktop/ibus/panel/emoji/load-emoji-at-startup</applyto>
++ <owner>ibus</owner>
++ <type>bool</type>
++ <default>true</default>
++ <locale name="C">
++ <short>Load the emoji data at the time of startup</short>
++ <long>Load the emoji data at the time of startup if true.
++ About 10MB memory is needed to load the data.
++ Load the emoji data when open the emoji dialog at the
++ beginning if false.
++ </long>
++ </locale>
++ </schema>
++ <schema>
++ <key>/schemas/desktop/ibus/panel/emoji/load-unicode-at-startup</key>
++ <applyto>/desktop/ibus/panel/emoji/load-unicode-at-startup</applyto>
++ <owner>ibus</owner>
++ <type>bool</type>
++ <default>false</default>
++ <locale name="C">
++ <short>Load the Unicode data at the time of startup</short>
++ <long>Load the Unicode data at the time of startup if true.
++ About 15MB memory is needed to load the data.
++ Load the Unicode data when open the emoji dialog at the
++ beginning if false.
++ The Unicode data is always loaded after the emoji data
++ is loaded even if true.
++ </long>
++ </locale>
++ </schema>
+ <schema>
+ <key>/schemas/desktop/ibus/general/embed_preedit_text</key>
+ <applyto>/desktop/ibus/general/embed_preedit_text</applyto>
+diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
+index 50700121..1fbf6cfc 100644
+--- a/ui/gtk3/panelbinding.vala
++++ b/ui/gtk3/panelbinding.vala
+@@ -35,6 +35,10 @@ class PanelBinding : IBus.PanelService {
+ private const uint PRELOAD_ENGINES_DELAY_TIME = 30000;
+ private GLib.List<BindingCommon.Keybinding> m_keybindings =
+ new GLib.List<BindingCommon.Keybinding>();
++ private bool m_load_emoji_at_startup;
++ private bool m_loaded_emoji = false;
++ private bool m_load_unicode_at_startup;
++ private bool m_loaded_unicode = false;
+
+ public PanelBinding(IBus.Bus bus) {
+ GLib.assert(bus.is_connected());
+@@ -109,6 +113,14 @@ class PanelBinding : IBus.PanelService {
+ m_settings_emoji.changed["partial-match-condition"].connect((key) => {
+ set_emoji_partial_match();
+ });
++
++ m_settings_emoji.changed["load-emoji-at-startup"].connect((key) => {
++ set_load_emoji_at_startup();
++ });
++
++ m_settings_emoji.changed["load-unicode-at-startup"].connect((key) => {
++ set_load_unicode_at_startup();
++ });
+ }
+
+
+@@ -148,7 +160,11 @@ class PanelBinding : IBus.PanelService {
+ IBusEmojier.set_annotation_lang(
+ m_settings_emoji.get_string("lang"));
+ m_emojier_set_emoji_lang_id = 0;
+- IBusEmojier.load_unicode_dict();
++ m_loaded_emoji = true;
++ if (m_load_unicode_at_startup && !m_loaded_unicode) {
++ IBusEmojier.load_unicode_dict();
++ m_loaded_unicode = true;
++ }
+ return false;
+ });
+ }
+@@ -164,7 +180,21 @@ class PanelBinding : IBus.PanelService {
+ }
+
+
++ private void set_load_emoji_at_startup() {
++ m_load_emoji_at_startup =
++ m_settings_emoji.get_boolean("load-emoji-at-startup");
++ }
++
++
++ private void set_load_unicode_at_startup() {
++ m_load_unicode_at_startup =
++ m_settings_emoji.get_boolean("load-unicode-at-startup");
++ }
++
+ public void load_settings() {
++
++ set_load_emoji_at_startup();
++ set_load_unicode_at_startup();
+ BindingCommon.unbind_switch_shortcut(BindingCommon.KeyEventFuncType.ANY,
+ m_keybindings);
+ bind_emoji_shortcut();
+@@ -172,7 +202,8 @@ class PanelBinding : IBus.PanelService {
+ m_settings_emoji,
+ ref m_css_provider);
+ set_emoji_favorites();
+- set_emoji_lang();
++ if (m_load_emoji_at_startup && !m_loaded_emoji)
++ set_emoji_lang();
+ set_emoji_partial_match();
+ }
+
+@@ -191,6 +222,12 @@ class PanelBinding : IBus.PanelService {
+
+
+ private void show_emojier(Gdk.Event event) {
++ if (!m_loaded_emoji)
++ set_emoji_lang();
++ if (!m_loaded_unicode && m_loaded_emoji) {
++ IBusEmojier.load_unicode_dict();
++ m_loaded_unicode = true;
++ }
+ m_emojier = new IBusEmojier();
+ string emoji = m_emojier.run(m_real_current_context_path, event);
+ if (emoji == null) {
+--
+2.14.3
+
diff --git a/ibus-xx-emoji-harfbuzz.patch b/ibus-xx-emoji-harfbuzz.patch
index 89a37b1..e948a6f 100644
--- a/ibus-xx-emoji-harfbuzz.patch
+++ b/ibus-xx-emoji-harfbuzz.patch
@@ -1,6 +1,6 @@
-From c6c1e8ea01c8466dc97d7549e77538e2d7ec872a Mon Sep 17 00:00:00 2001
+From 158e06a10726a10393f1f6dd7237457b0b601f84 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Mon, 29 Jan 2018 18:27:09 +0900
+Date: Wed, 21 Feb 2018 15:39:49 +0900
Subject: [PATCH] Integrate custom rendering to use HarfBuzz glyph info
IBusFontSet offers FcFontSet, glyph info with HarfBuzz and rendering
@@ -20,11 +20,11 @@ Need configure --enable-harfbuzz-for-emoji option to enable this feature.
bindings/vala/Makefile.am | 83 +++
bindings/vala/ibus-fontset-1.0.deps | 1 +
configure.ac | 29 +
- ui/gtk3/Makefile.am | 32 +
+ ui/gtk3/Makefile.am | 36 ++
ui/gtk3/emojier.vala | 111 ++++
ui/gtk3/ibusfontset.c | 1030 ++++++++++++++++++++++++++++++++
ui/gtk3/ibusfontset.h | 302 ++++++++++
- 8 files changed, 1589 insertions(+)
+ 8 files changed, 1593 insertions(+)
create mode 100644 bindings/vala/IBusFontSet-1.0.metadata
create mode 100644 bindings/vala/ibus-fontset-1.0.deps
create mode 100644 ui/gtk3/ibusfontset.c
@@ -202,19 +202,19 @@ index bd41069b..243396ff 100644
])
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
-index 786b80e6..cd1e9c2c 100644
+index 0a8f4200..8bb2345d 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
-@@ -156,6 +156,8 @@ EXTRA_DIST = \
- $(man_seven_in_files) \
- emojierapp.vala \
+@@ -159,6 +159,8 @@ EXTRA_DIST = \
+ extension.vala \
+ gtkextension.xml.in \
gtkpanel.xml.in \
+ ibusfontset.c \
+ ibusfontset.h \
notification-item.xml \
notification-watcher.xml \
- $(NULL)
-@@ -198,6 +200,36 @@ emojierapp.o: $(srcdir)/emojierapp.c
+ panelbinding.vala \
+@@ -247,6 +249,40 @@ panelbinding.o: $(srcdir)/panelbinding.c
$(AM_V_CC_no)$(COMPILE) -c -o $@ $<
$(NULL)
@@ -227,6 +227,10 @@ index 786b80e6..cd1e9c2c 100644
+ ibusfontset.c \
+ $(NULL)
+
++ibus_extension_gtk3_SOURCES += \
++ ibusfontset.c \
++ $(NULL)
++
+AM_CFLAGS += \
+ @CAIRO_CFLAGS@ \
+ @FONTCONFIG_CFLAGS@ \
@@ -252,7 +256,7 @@ index 786b80e6..cd1e9c2c 100644
man_seven_DATA =$(man_seven_files:.7=.7.gz)
man_sevendir = $(mandir)/man7
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 555ea68f..0a703383 100644
+index c85dfa86..86482543 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -99,16 +99,103 @@ public class IBusEmojier : Gtk.ApplicationWindow {
@@ -367,7 +371,7 @@ index 555ea68f..0a703383 100644
}
private class ESelectedLabel : EWhiteLabel {
public ESelectedLabel(string text) {
-@@ -307,6 +395,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -313,6 +401,9 @@ public class IBusEmojier : Gtk.ApplicationWindow {
private static bool m_show_unicode = false;
private static LoadProgressObject m_unicode_progress_object;
private static bool m_loaded_unicode = false;
@@ -377,7 +381,7 @@ index 555ea68f..0a703383 100644
private ThemedRGBA m_rgba;
private Gtk.Box m_vbox;
-@@ -2064,6 +2155,22 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -2070,6 +2161,22 @@ public class IBusEmojier : Gtk.ApplicationWindow {
}
@@ -400,7 +404,7 @@ index 555ea68f..0a703383 100644
public static bool has_loaded_emoji_dict() {
if (m_emoji_to_data_dict == null)
return false;
-@@ -2094,6 +2201,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
+@@ -2100,6 +2207,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
int font_size = font_desc.get_size() / Pango.SCALE;
if (font_size != 0)
m_emoji_font_size = font_size;
diff --git a/ibus.spec b/ibus.spec
index 0b17381..455a95d 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -30,7 +30,7 @@
Name: ibus
Version: 1.5.17
-Release: 8%{?dist}
+Release: 9%{?dist}
Summary: Intelligent Input Bus for Linux OS
License: LGPLv2+
Group: System Environment/Libraries
@@ -38,16 +38,17 @@ URL: https://github.com/ibus/%name/wiki
Source0: https://github.com/ibus/%name/releases/download/%{version}/%{name}-%{version}.tar.gz
Source1: %{name}-xinput
Source2: %{name}.conf.5
+Source3: https://fujiwara.fedorapeople.org/ibus/po/%{name}-po-1.5.17-20180221.tar.gz
# Will remove the annotation tarball once the rpm is available on Fedora
# Upstreamed patches.
# Patch0: %%{name}-HEAD.patch
Patch0: %{name}-HEAD.patch
-# Under testing #1349148 #1385349 #1350291 #1406699 #1432252
-Patch1: %{name}-1385349-segv-bus-proxy.patch
%if %with_emoji_harfbuzz
# Under testing self rendering until Pango, Fontconfig, Cairo are stable
-Patch2: %{name}-xx-emoji-harfbuzz.patch
+Patch1: %{name}-xx-emoji-harfbuzz.patch
%endif
+# Under testing #1349148 #1385349 #1350291 #1406699 #1432252
+Patch2: %{name}-1385349-segv-bus-proxy.patch
BuildRequires: gettext-devel
BuildRequires: libtool
@@ -243,10 +244,12 @@ The ibus-devel-docs package contains developer documentation for IBus
# %%patch0 -p1
%patch0 -p1
# cp client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c ||
-%patch1 -p1 -z .segv
%if %with_emoji_harfbuzz
-%patch2 -p1 -z .hb
+%patch1 -p1 -z .hb
%endif
+%patch2 -p1 -z .segv
+
+zcat %SOURCE3 | tar xfvp -
# prep test
diff client/gtk2/ibusimcontext.c client/gtk3/ibusimcontext.c
@@ -360,6 +363,7 @@ dconf update || :
%{_libexecdir}/ibus-engine-simple
%{_libexecdir}/ibus-dconf
%{_libexecdir}/ibus-portal
+%{_libexecdir}/ibus-extension-gtk3
%{_libexecdir}/ibus-ui-emojier
%{_libexecdir}/ibus-ui-gtk3
%{_libexecdir}/ibus-x11
@@ -420,6 +424,10 @@ dconf update || :
%{_datadir}/gtk-doc/html/*
%changelog
+* Wed Feb 21 2018 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.17-9
+- Added panel extension for emoji keybinding not to depen on desktops
+- Showed Unicode code points on Unicode name list
+
* Tue Feb 13 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 1.5.17-8
- Remove useless requires
diff --git a/sources b/sources
index b11174b..5f3638d 100644
--- a/sources
+++ b/sources
@@ -1 +1,2 @@
-SHA512 (ibus-1.5.17.tar.gz) = 8a7e4fabbcb2096e647b1fb7487c92882bd320a4d777f2765817378abec2e60cafd63364c881fefc2805ff2baa6b28b15ee0710587662a3e65eeb60ead19496c
+8bb26453d0d1fa58e56c22668aaa8786 ibus-1.5.17.tar.gz
+c485c179e612ffd07cf6a6c567a928f1 ibus-po-1.5.17-20180221.tar.gz
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-05-31 2:06 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-31 2:06 [rpms/ibus] autotool: Added panel extension for emoji keybinding not to depen on desktops Takao Fujiwara
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox