public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Takao Fujiwara <tfujiwar@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/ibus] autotool: Delete upstreamed patches
Date: Sun, 31 May 2026 02:08:59 GMT	[thread overview]
Message-ID: <178019333938.1.11962536083669417757.rpms-ibus-eb9b4a574f42@fedoraproject.org> (raw)

A new commit has been pushed.

Repo   : rpms/ibus
Branch : autotool
Commit : eb9b4a574f42bac93b2262a06695ccc772232321
Author : Takao Fujiwara <tfujiwar@redhat.com>
Date   : 2025-08-22T09:48:10+09:00
Stats  : +0/-6030 in 1 file(s)
URL    : https://src.fedoraproject.org/rpms/ibus/c/eb9b4a574f42bac93b2262a06695ccc772232321?branch=autotool

Log:
Delete upstreamed patches

---
diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
deleted file mode 100644
index 9ea8b3c..0000000
--- a/ibus-HEAD.patch
+++ /dev/null
@@ -1,6030 +0,0 @@
-From d0ad4e6e519439f034d08fd6531403027bd7fbde Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Fri, 18 Apr 2025 16:36:43 +0900
-Subject: [PATCH] ui/gtk3: Fix Exit and Restart menu items in Wayland input-method V2
-
----
- tools/main.vala    |  1 +
- ui/gtk3/panel.vala | 18 +++++++-----------
- 2 files changed, 8 insertions(+), 11 deletions(-)
-
-diff --git a/tools/main.vala b/tools/main.vala
-index 4b5bcff2..a7c6d95d 100644
---- a/tools/main.vala
-+++ b/tools/main.vala
-@@ -277,6 +277,7 @@ bool start_daemon_in_wayland(bool     restart,
-                           "directly.\n");
-         }
-         bus = null;
-+        Posix.sleep(3);
-     } else if (bus != null) {
-         stderr.printf("%s\n".printf(_("IBus is running.")));
-         Posix.exit(Posix.EXIT_FAILURE);
-diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
-index e42a166e..ab514bab 100644
---- a/ui/gtk3/panel.vala
-+++ b/ui/gtk3/panel.vala
-@@ -1524,25 +1524,21 @@ class Panel : IBus.PanelService {
-             string[] _args = {};
-             _args += binary;
-             _args += args;
--            if (args == "exit" || args == "restart")
--                _args += "--type=kde-wayland";
--            string? standard_out = null;
--            string? standard_error = null;
-+            // FIXME: stdout & stderr causes a dead lock with `ibus restart`
-+            // in Wayland input-method V2.
-             GLib.Process.spawn_sync(null,
-                                     _args,
-                                     GLib.Environ.get(),
--                                    0,
-+                                    GLib.SpawnFlags.SEARCH_PATH_FROM_ENVP,
-+                                    null,
-+                                    null,
-                                     null,
--                                    out standard_out,
--                                    out standard_error,
-                                     null);
--            if (standard_out != null)
--                print(standard_out);
--            if (standard_error != null)
--                warning("Execute %s failed! %s", binary, standard_error);
-         } catch (GLib.SpawnError e) {
-             warning("Execute %s failed! %s", binary, e.message);
-         }
-+        IBus.quit();
-+        Gtk.main_quit();
-     }
- 
-     private void append_preferences_menu(Gtk.Menu menu) {
--- 
-2.49.0
-
-From b4f51b69f03b18fadf4bbd82abc7b7e92fae44f0 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Sat, 3 May 2025 11:54:21 +0900
-Subject: [PATCH] bus/inputcontext: Free data in bus_input_context_forward_process_key_event
-
-Should free data in case of no focuses and engines.
----
- bus/inputcontext.c | 6 ++++--
- 1 file changed, 4 insertions(+), 2 deletions(-)
-
-diff --git a/bus/inputcontext.c b/bus/inputcontext.c
-index c551fbc8..41b47540 100644
---- a/bus/inputcontext.c
-+++ b/bus/inputcontext.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) 2015-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2008-2024 Red Hat, Inc.
-+ * Copyright (C) 2015-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2025 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
-@@ -3564,5 +3564,7 @@ bus_input_context_forward_process_key_event (BusInputContext *context,
-                 (GAsyncReadyCallback)
-                         _forward_process_key_event_reply_cb,
-                 data);
-+    } else {
-+        g_slice_free (ProcessKeyEventData, data);
-     }
- }
--- 
-2.49.0
-
-From e7222db9b72dfd08d7ef1a03d5b6baa046d025f0 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 22 May 2025 23:23:48 +0900
-Subject: [PATCH] ui/gtk3: Update Plasma setup message
-
-BUG=https://github.com/ibus/ibus/issues/2753
----
- ui/gtk3/panel.vala | 11 +++++------
- 1 file changed, 5 insertions(+), 6 deletions(-)
-
-diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
-index ab514bab..363834e9 100644
---- a/ui/gtk3/panel.vala
-+++ b/ui/gtk3/panel.vala
-@@ -926,12 +926,11 @@ class Panel : IBus.PanelService {
-             var format =
-                     _("IBus should be called from the desktop session in " +
-                       "%s. For KDE, you can launch '%s' " +
--                      "utility and go to \"Input Devices\" -> " +
--                      "\"Virtual Keyboard\" section and select " +
-+                      "utility and go to \"Input & Output\" -> \"Keyboard\" " +
-+                      "-> \"Virtual Keyboard\" section and select " +
-                       "\"%s\" icon and click \"Apply\" button to " +
-                       "configure IBus in %s. For other desktop " +
--                      "sessions, you can copy the 'Exec=' line in %s file " +
--                      "to a configuration file of the session. " +
-+                      "sessions, you can run \"%s\" command. " +
-                       "Please refer each document about the \"Wayland " +
-                       "input method\" configuration. Before you configure " +
-                       "the \"Wayland input method\", you should make sure " +
-@@ -939,10 +938,10 @@ class Panel : IBus.PanelService {
-                       "variables are unset in the desktop session.");
-                 message = format.printf(
-                         "Wayland",
--                        "systemsettings5",
-+                        "systemsettings",
-                         "IBus Wayland",
-                         "Wayland",
--                        "org.freedesktop.IBus.Panel.Wayland.Gtk3.desktop");
-+                        "ibus start");
-         } else if (m_is_wayland && m_is_wayland_im && !is_gnome()) {
-             if (Environment.get_variable("QT_IM_MODULE") == "ibus") {
-                 var format =
--- 
-2.49.0
-
-From 7feb57a8330d0969f82c742a2c1c0aeb120971fc Mon Sep 17 00:00:00 2001
-From: matiwari <matiwari@redhat.com>
-Date: Tue, 29 Apr 2025 12:52:25 +0530
-Subject: [PATCH] src/ibusobservedpath: Free IBusObservedPath objects during traversal
-
-BUG=https://github.com/ibus/ibus/issues/2755
----
- src/ibusobservedpath.c | 9 ++++++++-
- 1 file changed, 8 insertions(+), 1 deletion(-)
-
-diff --git a/src/ibusobservedpath.c b/src/ibusobservedpath.c
-index 42192431..224bfb8b 100644
---- a/src/ibusobservedpath.c
-+++ b/src/ibusobservedpath.c
-@@ -79,7 +79,12 @@ ibus_observed_path_init (IBusObservedPath *path)
- static void
- ibus_observed_path_destroy (IBusObservedPath *path)
- {
-+    IBusObservedPathPrivate *priv;
-     g_free (path->path);
-+    if (IBUS_IS_OBSERVED_PATH (path)) {
-+        priv = IBUS_OBSERVED_PATH_GET_PRIVATE (path);
-+        g_clear_pointer (&priv->file_hash_list, g_free);
-+    }
-     IBUS_OBJECT_CLASS (ibus_observed_path_parent_class)->destroy (IBUS_OBJECT (path));
- }
- 
-@@ -349,6 +354,8 @@ ibus_observed_path_traverse (IBusObservedPath *path,
-                                    ibus_observed_path_traverse (sub, dir_only));
-         } else if (sub->is_exist && !dir_only) {
-             paths = g_list_append (paths, sub);
-+        } else {
-+            g_object_unref (sub);
-         }
-     }
-     g_dir_close (dir);
-@@ -513,7 +520,7 @@ ibus_observed_path_new (const gchar *path,
-         priv->file_hash_list[i + 1] = 0;
-         ++i;
-     }
--    g_list_free_full (file_list, (GDestroyNotify)ibus_observed_path_destroy);
-+    g_list_free_full (file_list, (GDestroyNotify)g_object_unref);
- 
-     if (fill_stat)
-         ibus_observed_path_fill_stat (op);
--- 
-2.49.0
-
-From c5d98f98ecc98f8c726b5d9631464305306c8fb1 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Sun, 25 May 2025 19:08:24 +0900
-Subject: [PATCH 1/3] ui/gtk3: Delete Vala deprecated APIs before 0.34
-
-Vala 0.34 has been available since 2018 and now the deprecated APIs
-can be deleted safely for 5 years.
-
-BUG=https://github.com/ibus/ibus/pull/2761
----
- ui/gtk3/bindingcommon.vala  | 11 +-----
- ui/gtk3/candidatepanel.vala |  7 ----
- ui/gtk3/emojier.vala        | 34 ----------------
- ui/gtk3/propertypanel.vala  | 61 -----------------------------
- ui/gtk3/switcher.vala       | 78 -------------------------------------
- 5 files changed, 2 insertions(+), 189 deletions(-)
-
-diff --git a/ui/gtk3/bindingcommon.vala b/ui/gtk3/bindingcommon.vala
-index 8c332542..727e1257 100644
---- a/ui/gtk3/bindingcommon.vala
-+++ b/ui/gtk3/bindingcommon.vala
-@@ -76,17 +76,10 @@ class BindingCommon {
-                 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
-+            // workaround a bug in gdk vapi vala > 0.18
-+            // https://bugzilla.gnome.org/show_bug.cgi?id=677559
-             Gdk.Keymap.get_for_display(Gdk.Display.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;
-         }
- 
-diff --git a/ui/gtk3/candidatepanel.vala b/ui/gtk3/candidatepanel.vala
-index 5ba8cf36..aa1a34ce 100644
---- a/ui/gtk3/candidatepanel.vala
-+++ b/ui/gtk3/candidatepanel.vala
-@@ -508,17 +508,10 @@ public class CandidatePanel : Gtk.Box{
-         // Use get_monitor_geometry() instead of get_monitor_area().
-         // get_monitor_area() excludes docks, but the lookup window should be
-         // shown over them.
--#if VALA_0_34
-         Gdk.Monitor monitor = window.get_display().get_monitor_at_point(
-                 m_cursor_location.x,
-                 m_cursor_location.y);
-         monitor_area = monitor.get_geometry();
--#else
--        Gdk.Screen screen = Gdk.Screen.get_default();
--        int monitor_num = screen.get_monitor_at_point(m_cursor_location.x,
--                                                      m_cursor_location.y);
--        screen.get_monitor_geometry(monitor_num, out monitor_area);
--#endif
-         return monitor_area;
-     }
- 
-diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 519da3e2..1770de2f 100644
---- a/ui/gtk3/emojier.vala
-+++ b/ui/gtk3/emojier.vala
-@@ -29,11 +29,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-                 valign : Gtk.Align.FILL
-             );
-             this.motion_notify_event.connect((e) => {
--#if VALA_0_24
-                 Gdk.EventMotion pe = e;
--#else
--                Gdk.EventMotion *pe = &e;
--#endif
-                 if (m_mouse_x == pe.x_root && m_mouse_y == pe.y_root)
-                     return false;
-                 m_mouse_x = pe.x_root;
-@@ -1516,11 +1512,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-                     return false;
-                 if (m_lookup_table.get_cursor_pos() == index)
-                     return false;
--#if VALA_0_24
-                 Gdk.EventMotion pe = e;
--#else
--                Gdk.EventMotion *pe = &e;
--#endif
-                 if (m_mouse_x == pe.x_root && m_mouse_y == pe.y_root)
-                     return false;
-                 m_mouse_x = pe.x_root;
-@@ -1964,17 +1956,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         // Use get_monitor_geometry() instead of get_monitor_area().
-         // get_monitor_area() excludes docks, but the lookup window should be
-         // shown over them.
--#if VALA_0_34
-         Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor_at_point(
-                 m_cursor_location.x,
-                 m_cursor_location.y);
-         monitor_area = monitor.get_geometry();
--#else
--        Gdk.Screen screen = Gdk.Screen.get_default();
--        int monitor_num = screen.get_monitor_at_point(m_cursor_location.x,
--                                                      m_cursor_location.y);
--        screen.get_monitor_geometry(monitor_num, out monitor_area);
--#endif
-         return monitor_area;
-     }
- 
-@@ -2268,24 +2253,12 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         present_centralize(event);
- 
-         Gdk.Device pointer;
--#if VALA_0_34
-         Gdk.Seat seat = event.get_seat();
-         if (seat == null) {
-             var display = get_display();
-             seat = display.get_default_seat();
-         }
-         pointer = seat.get_pointer();
--#else
--        Gdk.Device device = event.get_device();
--        if (device == null) {
--            var display = get_display();
--            device = display.list_devices().data;
--        }
--        if (device.get_source() == Gdk.InputSource.KEYBOARD)
--            pointer = device.get_associated_device();
--        else
--            pointer = device;
--#endif
-         pointer.get_position_double(null,
-                                     out m_mouse_x,
-                                     out m_mouse_y);
-@@ -2521,17 +2494,10 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         get_allocation(out allocation);
-         Gdk.Rectangle monitor_area;
-         Gdk.Rectangle work_area;
--#if VALA_0_34
-         Gdk.Display display = get_display();
-         Gdk.Monitor monitor = display.get_monitor_at_window(this.get_window());
-         monitor_area = monitor.get_geometry();
-         work_area = monitor.get_workarea();
--#else
--        Gdk.Screen screen = Gdk.Screen.get_default();
--        int monitor_num = screen.get_monitor_at_window(this.get_window());
--        screen.get_monitor_geometry(monitor_num, out monitor_area);
--        work_area = screen.get_monitor_workarea(monitor_num);
--#endif
-         int x = (monitor_area.x + monitor_area.width - allocation.width)/2;
-         int y = (monitor_area.y + monitor_area.height
-                  - allocation.height)/2;
-diff --git a/ui/gtk3/propertypanel.vala b/ui/gtk3/propertypanel.vala
-index 20739b9c..f1ecd7f7 100644
---- a/ui/gtk3/propertypanel.vala
-+++ b/ui/gtk3/propertypanel.vala
-@@ -359,15 +359,10 @@ public class PropertyPanel : Gtk.Box {
-         m_toplevel.get_allocation(out allocation);
- 
-         Gdk.Rectangle monitor_area;
--#if VALA_0_34
-         // gdk_screen_get_monitor_workarea() no longer return the correct
-         // area from "_NET_WORKAREA" atom in GTK 3.22
-         Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor(0);
-         monitor_area = monitor.get_workarea();
--#else
--        Gdk.Screen screen = Gdk.Screen.get_default();
--        monitor_area = screen.get_monitor_workarea(0);
--#endif
-         int monitor_right = monitor_area.x + monitor_area.width;
-         int monitor_bottom = monitor_area.y + monitor_area.height;
-         int x, y;
-@@ -524,15 +519,10 @@ public class PropMenu : Gtk.Menu, IPropToolItem {
-     public new void popup(uint       button,
-                           uint32     activate_time,
-                           Gtk.Widget widget) {
--#if VALA_0_34
-         base.popup_at_widget(widget,
-                              Gdk.Gravity.SOUTH_WEST,
-                              Gdk.Gravity.NORTH_WEST,
-                              null);
--#else
--        m_parent_button = widget;
--        base.popup(null, null, menu_position, button, activate_time);
--#endif
-     }
- 
-     public override void destroy() {
-@@ -590,57 +580,6 @@ public class PropMenu : Gtk.Menu, IPropToolItem {
-             }
-         }
-     }
--
--#if !VALA_0_34
--    private void menu_position(Gtk.Menu menu,
--                               out int  x,
--                               out int  y,
--                               out bool push_in) {
--        var button = m_parent_button;
--        var screen = button.get_screen();
--        var monitor = screen.get_monitor_at_window(button.get_window());
--
--        Gdk.Rectangle monitor_location;
--        screen.get_monitor_geometry(monitor, out monitor_location);
--
--        button.get_window().get_origin(out x, out y);
--
--        Gtk.Allocation button_allocation;
--        button.get_allocation(out button_allocation);
--
--        x += button_allocation.x;
--        y += button_allocation.y;
--
--        int menu_width;
--        int menu_height;
--        menu.get_size_request(out menu_width, out menu_height);
--
--        if (x + menu_width >= monitor_location.width)
--            x -= menu_width - button_allocation.width;
--        else if (x - menu_width <= 0)
--            ;
--        else {
--            if (x <= monitor_location.width * 3 / 4)
--                ;
--            else
--                x -= menu_width - button_allocation.width;
--        }
--
--        if (y + button_allocation.height + menu_width
--                >= monitor_location.height)
--            y -= menu_height;
--        else if (y - menu_height <= 0)
--            y += button_allocation.height;
--        else {
--            if (y <= monitor_location.height * 3 / 4)
--                y += button_allocation.height;
--            else
--                y -= menu_height;
--        }
--
--        push_in = false;
--    }
--#endif
- }
- 
- public class PropToolButton : Gtk.ToolButton, IPropToolItem {
-diff --git a/ui/gtk3/switcher.vala b/ui/gtk3/switcher.vala
-index 99a2fa0b..d4a5fa11 100644
---- a/ui/gtk3/switcher.vala
-+++ b/ui/gtk3/switcher.vala
-@@ -235,7 +235,6 @@ class Switcher : Gtk.Window {
-         }
- 
-         Gdk.Device pointer;
--#if VALA_0_34
-         Gdk.Seat seat = event.get_seat();
-         if (seat == null) {
-             var display = get_display();
-@@ -262,54 +261,6 @@ class Switcher : Gtk.Window {
-                            null);
-         if (status != Gdk.GrabStatus.SUCCESS)
-             warning("Grab pointer failed! status = %d", status);
--#else
--        Gdk.Device device = event.get_device();
--        if (device == null) {
--            var display = get_display();
--            var device_manager = display.get_device_manager();
--/* The macro VALA_X_Y supports even numbers.
-- * http://git.gnome.org/browse/vala/commit/?id=294b374af6
-- */
--#if VALA_0_16
--            device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
--#else
--            unowned GLib.List<Gdk.Device> devices =
--                    device_manager.list_devices(Gdk.DeviceType.MASTER);
--            device = devices.data;
--#endif
--        }
--
--        Gdk.Device keyboard;
--        if (device.get_source() == Gdk.InputSource.KEYBOARD) {
--            keyboard = device;
--            pointer = device.get_associated_device();
--        } else {
--            pointer = device;
--            keyboard = device.get_associated_device();
--        }
--
--        Gdk.GrabStatus status;
--        // Grab all keyboard events
--        status = keyboard.grab(get_window(),
--                               Gdk.GrabOwnership.NONE,
--                               true,
--                               Gdk.EventMask.KEY_PRESS_MASK |
--                               Gdk.EventMask.KEY_RELEASE_MASK,
--                               null,
--                               Gdk.CURRENT_TIME);
--        if (status != Gdk.GrabStatus.SUCCESS)
--            warning("Grab keyboard failed! status = %d", status);
--        // Grab all pointer events
--        status = pointer.grab(get_window(),
--                              Gdk.GrabOwnership.NONE,
--                              true,
--                              Gdk.EventMask.BUTTON_PRESS_MASK |
--                              Gdk.EventMask.BUTTON_RELEASE_MASK,
--                              null,
--                              Gdk.CURRENT_TIME);
--        if (status != Gdk.GrabStatus.SUCCESS)
--            warning("Grab pointer failed! status = %d", status);
--#endif
- 
-         // Probably we can delete m_popup_delay_time in 1.6
-         pointer.get_position_double(null,
-@@ -322,12 +273,7 @@ class Switcher : Gtk.Window {
-         m_loop.run();
-         m_loop = null;
- 
--#if VALA_0_34
-         seat.ungrab();
--#else
--        keyboard.ungrab(Gdk.CURRENT_TIME);
--        pointer.ungrab(Gdk.CURRENT_TIME);
--#endif
- 
-         hide();
-         // Make sure the switcher is hidden before returning from this function.
-@@ -372,11 +318,7 @@ class Switcher : Gtk.Window {
-                 return true;
-             });
-             button.motion_notify_event.connect((e) => {
--#if VALA_0_24
-                 Gdk.EventMotion pe = e;
--#else
--                Gdk.EventMotion *pe = &e;
--#endif
-                 if (m_selected_engine == index)
-                     return false;
-                 if (!m_mouse_moved &&
-@@ -414,21 +356,12 @@ class Switcher : Gtk.Window {
- 
-         Gdk.Display display = Gdk.Display.get_default();
-         int screen_width = 0;
--#if VALA_0_34
-         // display.get_monitor_at_window() is null because of unrealized window
-         Gdk.Monitor monitor = display.get_primary_monitor();
-         if (monitor == null)
-             return;
-         Gdk.Rectangle area = monitor.get_geometry();
-         screen_width = area.width;
--#else
--        Gdk.Screen screen = (display != null) ?
--                display.get_default_screen() : null;
--
--        if (screen != null) {
--            screen_width = screen.get_width();
--        }
--#endif
- 
-         if (screen_width > 0 && max_label_width > (screen_width / 4)) {
-             max_label_width = screen_width / 4;
-@@ -503,14 +436,7 @@ class Switcher : Gtk.Window {
-     public override bool key_press_event(Gdk.EventKey e) {
-         bool retval = Gdk.EVENT_STOP;
- 
--/* Gdk.EventKey is changed to the pointer.
-- * https://git.gnome.org/browse/vala/commit/?id=598942f1
-- */
--#if VALA_0_24
-         Gdk.EventKey pe = e;
--#else
--        Gdk.EventKey *pe = &e;
--#endif
- 
-         if (m_popup_delay_time > 0) {
-             restore_window_position("pressed");
-@@ -557,11 +483,7 @@ class Switcher : Gtk.Window {
-     }
- 
-     public override bool key_release_event(Gdk.EventKey e) {
--#if VALA_0_24
-         Gdk.EventKey pe = e;
--#else
--        Gdk.EventKey *pe = &e;
--#endif
- 
-         if (KeybindingManager.primary_modifier_still_pressed((Gdk.Event) pe,
-             m_primary_modifier)) {
--- 
-2.49.0
-
-From cf6263e4742fa338fd9bbed78396bb29a70aeccb Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Sun, 1 Jun 2025 20:24:42 +0900
-Subject: [PATCH 2/3] Implement IBusMessage
-
-IBusEngine and IBusPanel can send IBusMessage to IBusPanel.
-Currently GtkPopup takes input focus in Wayland, and the Panel can only
-display non-focused popups with the Wayland panel protocol.
-
-While GTK allows the IM modules to flash the focused window to alert
-users of thier typing errors, individual IBus engines lack this
-capability. This is because engines typically only receive keyboard
-input (keysyms/keycodes) and not the associated window information.
-The proposed solution is to redirect the error messages to the system
-Panel. The Panel, having access to window management in Wayland and
-Xorg, could then handle the display of non-focused popup notifications.
-
-GNOME-Shell needs to handle the IBusMessage. As a temporary solution in
-GNOME, the compose sequence will be flashed to alert users as a
-workaround until the full implementation is completed.
-
-Also refactor "Free data in bus_input_context_forward_process_key_event".
-
-Fixes: https://github.com/ibus/ibus/commit/b4f51b69
-BUG=https://github.com/ibus/ibus/issues/2722
----
- bindings/vala/IBus-1.0-custom.vala |  40 +-
- bus/engineproxy.c                  |  40 +-
- bus/ibusimpl.c                     |  67 ++--
- bus/inputcontext.c                 |  34 +-
- bus/panelproxy.c                   |  37 +-
- bus/panelproxy.h                   |   7 +-
- src/Makefile.am                    |   6 +-
- src/ibusengine.c                   |  33 +-
- src/ibusengine.h                   |  36 +-
- src/ibusenginesimple.c             |  63 +++-
- src/ibusmessage.c                  | 562 +++++++++++++++++++++++++++++
- src/ibusmessage.h                  | 205 +++++++++++
- src/ibuspanelservice.c             | 128 ++++++-
- src/ibuspanelservice.h             |  38 +-
- src/ibustypes.h                    |  14 +
- ui/gtk3/Makefile.am                |   1 +
- ui/gtk3/emojier.vala               |  39 +-
- ui/gtk3/message.vala               | 353 ++++++++++++++++++
- ui/gtk3/panel.vala                 | 137 +++++--
- ui/gtk3/panelbinding.vala          |   5 +-
- 20 files changed, 1727 insertions(+), 118 deletions(-)
- create mode 100644 src/ibusmessage.c
- create mode 100644 src/ibusmessage.h
- create mode 100644 ui/gtk3/message.vala
-
-diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala
-index ec46fc90..8ced8a97 100644
---- a/bindings/vala/IBus-1.0-custom.vala
-+++ b/bindings/vala/IBus-1.0-custom.vala
-@@ -1,24 +1,38 @@
- namespace IBus {
-+        public class EmojiData : IBus.Serializable {
-+		[CCode (cname = "ibus_emoji_data_new",
-+                        has_construct_function = true)]
-+		public EmojiData (string first_property_name, ...);
-+	}
-+	public class ExtensionEvent : IBus.Serializable {
-+		[CCode (cname = "ibus_extension_event_new",
-+                        has_construct_function = true)]
-+		public ExtensionEvent (string first_property_name, ...);
-+	}
-+	public class Message : IBus.Serializable {
-+		[CCode (cname = "ibus_message_new",
-+                        has_construct_function = true)]
-+		public Message (uint domain,
-+                                uint code,
-+                                string? title,
-+                                string description,
-+                                ...);
-+	}
-+	public class PanelService : IBus.Service {
-+                public void
-+                panel_extension_register_keys(string first_property_name, ...);
-+	}
- 	// For some reason, ibus_text_new_from_static_string is hidden in GIR
- 	// https://github.com/ibus/ibus/commit/37e6e587
- 	[CCode (type_id = "ibus_text_get_type ()", cheader_filename = "ibus.h")]
- 	public class Text : IBus.Serializable {
--		[CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)]
-+		[CCode (cname = "ibus_text_new_from_static_string",
-+                        has_construct_function = false)]
- 		public Text.from_static_string (string str);
- 	}
--	public class ExtensionEvent : IBus.Serializable {
--		[CCode (cname = "ibus_extension_event_new", has_construct_function = true)]
--		public ExtensionEvent (string first_property_name, ...);
--	}
- 	public class XEvent : IBus.Serializable {
--		[CCode (cname = "ibus_x_event_new", has_construct_function = true)]
-+		[CCode (cname = "ibus_x_event_new",
-+                 has_construct_function = true)]
- 		public XEvent (string first_property_name, ...);
- 	}
--	public class PanelService : IBus.Service {
--                public void panel_extension_register_keys(string first_property_name, ...);
--	}
--        public class EmojiData : IBus.Serializable {
--		[CCode (cname = "ibus_emoji_data_new", has_construct_function = true)]
--		public EmojiData (string first_property_name, ...);
--	}
- }
-diff --git a/bus/engineproxy.c b/bus/engineproxy.c
-index 8bd6cefd..98ec63fe 100644
---- a/bus/engineproxy.c
-+++ b/bus/engineproxy.c
-@@ -95,6 +95,7 @@ enum {
-     REGISTER_PROPERTIES,
-     UPDATE_PROPERTY,
-     PANEL_EXTENSION,
-+    SEND_MESSAGE,
-     LAST_SIGNAL,
- };
- 
-@@ -458,6 +459,20 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class)
-                                 G_TYPE_FROM_CLASS (class),
-                                 bus_marshal_VOID__OBJECTv);
- 
-+    engine_signals[SEND_MESSAGE] =
-+        g_signal_new (I_("send-message"),
-+            G_TYPE_FROM_CLASS (class),
-+            G_SIGNAL_RUN_LAST,
-+            0,
-+            NULL, NULL,
-+            bus_marshal_VOID__VARIANT,
-+            G_TYPE_NONE,
-+            1,
-+            G_TYPE_VARIANT);
-+    g_signal_set_va_marshaller (engine_signals[SEND_MESSAGE],
-+                                G_TYPE_FROM_CLASS (class),
-+                                bus_marshal_VOID__VARIANTv);
-+
-     text_empty = ibus_text_new_from_static_string ("");
-     g_object_ref_sink (text_empty);
- 
-@@ -594,7 +609,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
- 
-     gint i;
-     for (i = 0; i < G_N_ELEMENTS (signals); i++) {
--        if (g_strcmp0 (signal_name, signals[i].signal_name) == 0) {
-+        if (!g_strcmp0 (signal_name, signals[i].signal_name)) {
-             g_signal_emit (engine, engine_signals[signals[i].signal_id], 0);
-             return;
-         }
-@@ -603,7 +618,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-     /* Handle D-Bus signals with parameters. Deserialize them and emit a glib
-      * signal.
-      */
--    if (g_strcmp0 (signal_name, "CommitText") == 0) {
-+    if (!g_strcmp0 (signal_name, "CommitText")) {
-         GVariant *arg0 = NULL;
-         g_variant_get (parameters, "(v)", &arg0);
-         g_return_if_fail (arg0 != NULL);
-@@ -616,7 +631,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "ForwardKeyEvent") == 0) {
-+    if (!g_strcmp0 (signal_name, "ForwardKeyEvent")) {
-         guint32 keyval = 0;
-         guint32 keycode = 0;
-         guint32 states = 0;
-@@ -631,7 +646,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "DeleteSurroundingText") == 0) {
-+    if (!g_strcmp0 (signal_name, "DeleteSurroundingText")) {
-         gint  offset_from_cursor = 0;
-         guint nchars = 0;
-         g_variant_get (parameters, "(iu)", &offset_from_cursor, &nchars);
-@@ -642,7 +657,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "UpdatePreeditText") == 0) {
-+    if (!g_strcmp0 (signal_name, "UpdatePreeditText")) {
-         GVariant *arg0 = NULL;
-         guint cursor_pos = 0;
-         gboolean visible = FALSE;
-@@ -664,7 +679,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "UpdateAuxiliaryText") == 0) {
-+    if (!g_strcmp0 (signal_name, "UpdateAuxiliaryText")) {
-         GVariant *arg0 = NULL;
-         gboolean visible = FALSE;
- 
-@@ -684,7 +699,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "UpdateLookupTable") == 0) {
-+    if (!g_strcmp0 (signal_name, "UpdateLookupTable")) {
-         GVariant *arg0 = NULL;
-         gboolean visible = FALSE;
- 
-@@ -705,7 +720,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "RegisterProperties") == 0) {
-+    if (!g_strcmp0 (signal_name, "RegisterProperties")) {
-         GVariant *arg0 = NULL;
-         g_variant_get (parameters, "(v)", &arg0);
-         g_return_if_fail (arg0 != NULL);
-@@ -723,7 +738,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "UpdateProperty") == 0) {
-+    if (!g_strcmp0 (signal_name, "UpdateProperty")) {
-         GVariant *arg0 = NULL;
-         g_variant_get (parameters, "(v)", &arg0);
-         g_return_if_fail (arg0 != NULL);
-@@ -738,7 +753,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
--    if (g_strcmp0 (signal_name, "PanelExtension") == 0) {
-+    if (!g_strcmp0 (signal_name, "PanelExtension")) {
-         GVariant *arg0 = NULL;
-         g_variant_get (parameters, "(v)", &arg0);
-         g_return_if_fail (arg0 != NULL);
-@@ -752,6 +767,11 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
-+    if (!g_strcmp0 (signal_name, "SendMessage")) {
-+        g_signal_emit (engine, engine_signals[SEND_MESSAGE], 0, parameters);
-+        return;
-+    }
-+
-     g_return_if_reached ();
- }
- 
-diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c
-index 5c9c52ce..fe521f2d 100644
---- a/bus/ibusimpl.c
-+++ b/bus/ibusimpl.c
-@@ -470,6 +470,19 @@ _panel_forward_process_key_event_cb (BusPanelProxy *panel,
-                                                  modifiers);
- }
- 
-+static void
-+_panel_send_message_cb (BusPanelProxy *panel,
-+                        GVariant      *parameters,
-+                        BusIBusImpl   *ibus)
-+{
-+    if (!ibus->panel) {
-+        g_warning ("Panel is not running.");
-+        return;
-+    }
-+    g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->panel));
-+    bus_panel_proxy_send_message_received (ibus->panel, parameters);
-+}
-+
- static void
- _registry_changed_cb (IBusRegistry *registry,
-                       BusIBusImpl  *ibus)
-@@ -556,26 +569,29 @@ _dbus_name_owner_changed_cb (BusDBusImpl   *dbus,
-                               G_CALLBACK (
-                                       _panel_panel_extension_register_keys_cb),
-                               ibus);
--            g_signal_connect (
--                    *panel,
--                    "update-preedit-text-received",
--                    G_CALLBACK (_panel_update_preedit_text_received_cb),
--                    ibus);
--            g_signal_connect (
--                    *panel,
--                    "update-lookup-table-received",
--                    G_CALLBACK (_panel_update_lookup_table_received_cb),
--                    ibus);
--            g_signal_connect (
--                    *panel,
--                    "update-auxiliary-text-received",
--                    G_CALLBACK (_panel_update_auxiliary_text_received_cb),
--                    ibus);
--            g_signal_connect (
--                    *panel,
--                    "forward-process-key-event",
--                    G_CALLBACK (_panel_forward_process_key_event_cb),
--                    ibus);
-+            g_signal_connect (*panel,
-+                              "update-preedit-text-received",
-+                              G_CALLBACK (
-+                                      _panel_update_preedit_text_received_cb),
-+                              ibus);
-+            g_signal_connect (*panel,
-+                              "update-lookup-table-received",
-+                              G_CALLBACK (
-+                                      _panel_update_lookup_table_received_cb),
-+                              ibus);
-+            g_signal_connect (*panel,
-+                              "update-auxiliary-text-received",
-+                              G_CALLBACK (
-+                                      _panel_update_auxiliary_text_received_cb),
-+                              ibus);
-+            g_signal_connect (*panel,
-+                              "forward-process-key-event",
-+                              G_CALLBACK (_panel_forward_process_key_event_cb),
-+                              ibus);
-+            g_signal_connect (*panel,
-+                              "send-message",
-+                              G_CALLBACK (_panel_send_message_cb),
-+                              ibus);
- 
-             if (ibus->focused_context != NULL) {
-                 context = ibus->focused_context;
-@@ -849,11 +865,20 @@ _context_panel_extension_cb (BusInputContext    *context,
-     bus_ibus_impl_set_panel_extension_mode (ibus, event);
- }
- 
-+static void
-+_context_send_message_cb (BusInputContext *context,
-+                          GVariant        *parameters,
-+                          BusIBusImpl     *ibus)
-+{
-+    _panel_send_message_cb (ibus->panel, parameters, ibus);
-+}
-+
- const static struct {
-     const gchar *name;
-     GCallback    callback;
- } context_signals [] = {
--    { "panel-extension",             G_CALLBACK (_context_panel_extension_cb) }
-+    { "panel-extension",             G_CALLBACK (_context_panel_extension_cb) },
-+    { "send-message",                G_CALLBACK (_context_send_message_cb) }
- };
- 
- /**
-diff --git a/bus/inputcontext.c b/bus/inputcontext.c
-index 41b47540..ee4fa99b 100644
---- a/bus/inputcontext.c
-+++ b/bus/inputcontext.c
-@@ -165,6 +165,7 @@ enum {
-     REQUEST_ENGINE,
-     SET_CONTENT_TYPE,
-     PANEL_EXTENSION,
-+    SEND_MESSAGE,
-     LAST_SIGNAL,
- };
- 
-@@ -767,6 +768,20 @@ bus_input_context_class_init (BusInputContextClass *class)
-                                 G_TYPE_FROM_CLASS (class),
-                                 bus_marshal_VOID__OBJECTv);
- 
-+    context_signals[SEND_MESSAGE] =
-+        g_signal_new (I_("send-message"),
-+            G_TYPE_FROM_CLASS (class),
-+            G_SIGNAL_RUN_LAST,
-+            0,
-+            NULL, NULL,
-+            bus_marshal_VOID__VARIANT,
-+            G_TYPE_NONE,
-+            1,
-+            G_TYPE_VARIANT);
-+    g_signal_set_va_marshaller (context_signals[SEND_MESSAGE],
-+                                G_TYPE_FROM_CLASS (class),
-+                                bus_marshal_VOID__VARIANTv);
-+
-     text_empty = ibus_text_new_from_string ("");
-     g_object_ref_sink (text_empty);
-     lookup_table_empty = ibus_lookup_table_new (9 /* page size */,
-@@ -2789,6 +2804,20 @@ _engine_panel_extension_cb (BusEngineProxy     *engine,
-     g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, event);
- }
- 
-+/**
-+ * _engine_send_message_cb:
-+ *
-+ * A function to be called when "send-message" glib signal is sent
-+ * from the engine object.
-+ */
-+static void
-+_engine_send_message_cb (BusEngineProxy  *engine,
-+                         GVariant        *parameters,
-+                         BusInputContext *context)
-+{
-+    g_signal_emit (context, context_signals[SEND_MESSAGE], 0, parameters);
-+}
-+
- static void
- _engine_show_preedit_text_cb (BusEngineProxy  *engine,
-                               BusInputContext *context)
-@@ -2978,6 +3007,7 @@ const static struct {
-     { "register-properties",      G_CALLBACK (_engine_register_properties_cb) },
-     { "update-property",          G_CALLBACK (_engine_update_property_cb) },
-     { "panel-extension",          G_CALLBACK (_engine_panel_extension_cb) },
-+    { "send-message",             G_CALLBACK (_engine_send_message_cb) },
-     { "destroy",                  G_CALLBACK (_engine_destroy_cb) }
- };
- 
-@@ -3549,9 +3579,9 @@ bus_input_context_forward_process_key_event (BusInputContext *context,
-                                              guint            keycode,
-                                              guint            modifiers)
- {
--    ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData);
-     g_assert (BUS_IS_INPUT_CONTEXT (context));
-     if (context->has_focus && context->engine && context->fake == FALSE) {
-+        ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData);
-         data->context = g_object_ref (context);
-         data->keyval = keyval;
-         data->keycode = keycode;
-@@ -3564,7 +3594,5 @@ bus_input_context_forward_process_key_event (BusInputContext *context,
-                 (GAsyncReadyCallback)
-                         _forward_process_key_event_reply_cb,
-                 data);
--    } else {
--        g_slice_free (ProcessKeyEventData, data);
-     }
- }
-diff --git a/bus/panelproxy.c b/bus/panelproxy.c
-index b76d6f76..997f5964 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-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2008-2024 Red Hat, Inc.
-+ * Copyright (C) 2017-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2025 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
-@@ -57,6 +57,7 @@ enum {
-     UPDATE_LOOKUP_TABLE_RECEIVED,
-     UPDATE_AUXILIARY_TEXT_RECEIVED,
-     FORWARD_PROCESS_KEY_EVENT,
-+    SEND_MESSAGE,
-     LAST_SIGNAL
- };
- 
-@@ -366,6 +367,19 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class)
-     g_signal_set_va_marshaller (panel_signals[FORWARD_PROCESS_KEY_EVENT],
-                                 G_TYPE_FROM_CLASS (class),
-                                 bus_marshal_VOID__UINT_UINT_UINTv);
-+
-+    panel_signals[SEND_MESSAGE] =
-+        g_signal_new (I_("send-message"),
-+            G_TYPE_FROM_CLASS (class),
-+            G_SIGNAL_RUN_LAST,
-+            0,
-+            NULL, NULL,
-+            bus_marshal_VOID__VARIANT,
-+            G_TYPE_NONE, 1,
-+            G_TYPE_VARIANT);
-+    g_signal_set_va_marshaller (panel_signals[SEND_MESSAGE],
-+                                G_TYPE_FROM_CLASS (class),
-+                                bus_marshal_VOID__VARIANTv);
- }
- 
- static void
-@@ -551,6 +565,11 @@ bus_panel_proxy_g_signal (GDBusProxy  *proxy,
-         return;
-     }
- 
-+    if (g_strcmp0 ("SendMessage", signal_name) == 0) {
-+        g_signal_emit (panel, panel_signals[SEND_MESSAGE], 0, parameters);
-+        return;
-+    }
-+
-     /* shound not be reached */
-     g_return_if_reached ();
- }
-@@ -1170,3 +1189,17 @@ bus_panel_proxy_candidate_clicked_lookup_table (BusPanelProxy *panel,
-                        G_DBUS_CALL_FLAGS_NONE,
-                        -1, NULL, NULL, NULL);
- }
-+
-+void
-+bus_panel_proxy_send_message_received (BusPanelProxy *panel,
-+                                       GVariant      *parameters)
-+{
-+    g_assert (BUS_IS_PANEL_PROXY (panel));
-+    g_assert (parameters);
-+
-+    g_dbus_proxy_call ((GDBusProxy *)panel,
-+                       "SendMessageReceived",
-+                       g_variant_ref (parameters),
-+                       G_DBUS_CALL_FLAGS_NONE,
-+                       -1, NULL, NULL, NULL);
-+}
-diff --git a/bus/panelproxy.h b/bus/panelproxy.h
-index 4d8afb98..ff205cf1 100644
---- a/bus/panelproxy.h
-+++ b/bus/panelproxy.h
-@@ -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-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2008-2018 Red Hat, Inc.
-+ * Copyright (C) 2017-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2025 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
-@@ -155,6 +155,9 @@ void             bus_panel_proxy_candidate_clicked_lookup_table
-                                                 guint              index,
-                                                 guint              button,
-                                                 guint              state);
-+void             bus_panel_proxy_send_message_received
-+                                               (BusPanelProxy     *panel,
-+                                                GVariant          *parameters);
- 
- G_END_DECLS
- #endif
-diff --git a/src/Makefile.am b/src/Makefile.am
-index bef269cb..519fc64e 100644
---- a/src/Makefile.am
-+++ b/src/Makefile.am
-@@ -3,8 +3,8 @@
- # ibus - The Input Bus
- #
- # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
--# Copyright (c) 2015-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
--# Copyright (c) 2007-2017 Red Hat, Inc.
-+# Copyright (c) 2015-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+# Copyright (c) 2007-2025 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
-@@ -93,6 +93,7 @@ ibus_sources =              \
-     ibuskeys.c              \
-     ibuskeyuni.c            \
-     ibuslookuptable.c       \
-+    ibusmessage.c           \
-     ibusobject.c            \
-     ibusobservedpath.c      \
-     ibuspanelservice.c      \
-@@ -151,6 +152,7 @@ ibus_headers =              \
-     ibuskeysyms-compat.h    \
-     ibuskeysyms.h           \
-     ibuslookuptable.h       \
-+    ibusmessage.h           \
-     ibusobject.h            \
-     ibusobservedpath.h      \
-     ibuspanelservice.h      \
-diff --git a/src/ibusengine.c b/src/ibusengine.c
-index c7a0ac78..4e13b23d 100644
---- a/src/ibusengine.c
-+++ b/src/ibusengine.c
-@@ -3,7 +3,7 @@
- /* ibus - The Input Bus
-  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
-  * Copyright (C) 2018-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2008-2021 Red Hat, Inc.
-+ * Copyright (C) 2008-2025 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
-@@ -302,6 +302,13 @@ static const gchar introspection_xml[] =
-     "    <signal name='PanelExtension'>"
-     "      <arg type='v' name='data' />"
-     "    </signal>"
-+    "    <signal name='SendMessage'>"
-+    "      <arg type='v' name='message' />"
-+    "      <annotation name='org.gtk.GDBus.Since'\n"
-+    "          value='1.5.33' />\n"
-+    "      <annotation name='org.gtk.GDBus.DocString'\n"
-+    "          value='Stability: Unstable' />\n"
-+    "    </signal>"
-     /* FIXME properties */
-     "    <property name='ContentType' type='(uu)' access='write' />"
-     "    <property name='FocusId' type='(b)' access='read' />"
-@@ -1812,12 +1819,17 @@ ibus_engine_emit_signal (IBusEngine  *engine,
-                          const gchar *signal_name,
-                          GVariant    *parameters)
- {
-+    GError *error = NULL;
-     ibus_service_emit_signal ((IBusService *)engine,
-                               NULL,
-                               IBUS_INTERFACE_ENGINE,
-                               signal_name,
-                               parameters,
--                              NULL);
-+                              &error);
-+    if (error) {
-+        g_warning ("Failed to emit %s signal: %s", signal_name, error->message);
-+        g_error_free (error);
-+    }
- }
- 
- static void
-@@ -2199,3 +2211,20 @@ ibus_engine_get_name (IBusEngine *engine)
-     g_return_val_if_fail (IBUS_IS_ENGINE (engine), NULL);
-     return engine->priv->engine_name;
- }
-+
-+void
-+ibus_engine_send_message (IBusEngine  *engine,
-+                          IBusMessage *message)
-+{
-+    GVariant *variant;
-+
-+    g_return_if_fail (IBUS_IS_ENGINE (engine));
-+    g_return_if_fail (IBUS_IS_MESSAGE (message));
-+    variant = ibus_serializable_serialize ((IBusSerializable *)message);
-+    ibus_engine_emit_signal (engine,
-+                             "SendMessage",
-+                              g_variant_new ("(v)", variant));
-+    if (g_object_is_floating (message)) {
-+        g_object_unref (message);
-+    }
-+}
-diff --git a/src/ibusengine.h b/src/ibusengine.h
-index 6af0e856..cdc96331 100644
---- a/src/ibusengine.h
-+++ b/src/ibusengine.h
-@@ -2,8 +2,8 @@
- /* vim:set et sts=4: */
- /* ibus - The Input Bus
-  * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright (C) 2012-2022 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2008-2022 Red Hat, Inc.
-+ * Copyright (C) 2012-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2008-2025 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
-@@ -43,6 +43,7 @@
- #include "ibusservice.h"
- #include "ibusattribute.h"
- #include "ibuslookuptable.h"
-+#include "ibusmessage.h"
- #include "ibusproplist.h"
- 
- /*
-@@ -65,6 +66,24 @@
- 
- G_BEGIN_DECLS
- 
-+/**
-+ * IBusEngineMsgCode:
-+ * @IBUS_ENGINE_MSG_CODE_GENERAL: Generic message for Engine
-+ * @IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE: User's typing failure
-+ *         against the definition of the compose files.
-+ *
-+ * Message codes in the `IBusMessageDomain` domain for Engine
-+ * See also #IBusMessage, ibus_engine_send_message()
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+typedef enum
-+{
-+  IBUS_ENGINE_MSG_CODE_GENERAL,
-+  IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE
-+} IBusEngineMsgCode;
-+
- typedef struct _IBusEngine IBusEngine;
- typedef struct _IBusEngineClass IBusEngineClass;
- typedef struct _IBusEnginePrivate IBusEnginePrivate;
-@@ -469,5 +488,18 @@ void ibus_engine_get_content_type       (IBusEngine         *engine,
-  */
- const gchar *ibus_engine_get_name       (IBusEngine         *engine);
- 
-+/**
-+ * ibus_engine_send_message:
-+ * @engine: An #IBusEngine.
-+ * @message: An #IBusMessage.
-+ *
-+ * Send a message to the Engine for the focus-less notification popup.
-+ * This is used for the user errors in Wayland mainly but in Xorg too.
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+void         ibus_engine_send_message   (IBusEngine         *engine,
-+                                         IBusMessage        *message);
- G_END_DECLS
- #endif
-diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
-index e93c61f5..4e0ba40f 100644
---- a/src/ibusenginesimple.c
-+++ b/src/ibusenginesimple.c
-@@ -3,7 +3,7 @@
- /* ibus - The Input Bus
-  * Copyright (C) 2014 Peng Huang <shawn.p.huang@gmail.com>
-  * Copyright (C) 2015-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2014-2017 Red Hat, Inc.
-+ * Copyright (C) 2014-2025 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
-@@ -36,6 +36,7 @@
- 
- #include <memory.h>
- #include <stdlib.h>
-+#include <glib/gi18n-lib.h>
- 
- #define IBUS_ENGINE_SIMPLE_GET_PRIVATE(o)  \
-    ((IBusEngineSimplePrivate *)ibus_engine_simple_get_instance_private (o))
-@@ -204,6 +205,27 @@ ibus_engine_simple_destroy (IBusEngineSimple *simple)
-         IBUS_OBJECT (simple));
- }
- 
-+static void
-+ibus_engine_simple_send_message_with_code (IBusEngineSimple *simple,
-+                                           IBusEngineMsgCode code)
-+{
-+    IBusMessage *message;
-+
-+    g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple));
-+    message = ibus_message_new (
-+            IBUS_MESSAGE_DOMAIN_ENGINE,
-+            code,
-+            _("Detect unregistered character in your compose sequence"),
-+            _("The character you just input is not recognized as a valid " \
-+              "part of the currently active compose sequence and the " \
-+              "character was cancelled. Try inputting the correct character " \
-+              "to the compose sequence again, or press Escape key to " \
-+              "terminate whole the compose sequence."),
-+             "timeout", 5,
-+             NULL);
-+    ibus_engine_send_message (IBUS_ENGINE (simple), message);
-+}
-+
- static void
- ibus_engine_simple_focus_in (IBusEngine *engine)
- {
-@@ -677,7 +699,9 @@ no_sequence_matches (IBusEngineSimple *simple,
-         priv->compose_buffer[0] = 0;
-         if (n_compose > 1) {
-             /* Invalid sequence */
--            /* FIXME beep_window (event->window); */
-+            ibus_engine_simple_send_message_with_code (
-+                    simple,
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-             ibus_engine_simple_update_preedit_text (simple);
-             return TRUE;
-         }
-@@ -948,7 +972,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                 priv->modifiers_dropped = TRUE;
-             } else {
-                 /* invalid hex sequence */
--                /* FIXME beep_window (event->window); */
-+                ibus_engine_simple_send_message_with_code (
-+                        simple,
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-                 g_string_set_size (priv->tentative_match, 0);
-                 g_clear_pointer (&priv->tentative_emoji, g_free);
-                 priv->in_hex_sequence = FALSE;
-@@ -971,7 +997,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                 priv->modifiers_dropped = TRUE;
-             } else {
-                 /* invalid hex sequence */
--                /* FIXME beep_window (event->window); */
-+                ibus_engine_simple_send_message_with_code (
-+                        simple,
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-                 g_string_set_size (priv->tentative_match, 0);
-                 g_clear_pointer (&priv->tentative_emoji, g_free);
-                 priv->in_hex_sequence = FALSE;
-@@ -1020,7 +1048,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-         }
-         else if (is_hex_end) {
-             /* invalid hex sequence */
--            /* FIXME beep_window (event->window); */
-+            ibus_engine_simple_send_message_with_code (
-+                    simple,
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-             g_string_set_size (priv->tentative_match, 0);
-             g_clear_pointer (&priv->tentative_emoji, g_free);
-             priv->in_hex_sequence = FALSE;
-@@ -1119,7 +1149,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-         else {
-             /* invalid hex sequence */
-             if (n_compose > 0) {
--                /* FIXME beep_window (event->window); */
-+                ibus_engine_simple_send_message_with_code (
-+                        simple,
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-                 g_string_set_size (priv->tentative_match, 0);
-                 priv->in_hex_sequence = FALSE;
-                 priv->compose_buffer[0] = 0;
-@@ -1184,7 +1216,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             return TRUE;
-         } else if (!is_hex_end) {
-             /* non-hex character in hex sequence */
--            /* FIXME beep_window (event->window); */
-+            ibus_engine_simple_send_message_with_code (
-+                    simple,
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-             return TRUE;
-         }
-     } else if (priv->in_emoji_sequence) {
-@@ -1243,15 +1277,18 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                     return TRUE;
-                 } else {
-                     /* invalid hex sequence */
--                    /* FIXME beep_window (event->window); */
-+                    ibus_engine_simple_send_message_with_code (
-+                            simple,
-+                            IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-                     g_string_set_size (priv->tentative_match, 0);
-                     priv->in_hex_sequence = FALSE;
-                     priv->compose_buffer[0] = 0;
-                 }
-+            } else if (!check_hex (simple, n_compose)) {
-+                ibus_engine_simple_send_message_with_code (
-+                        simple,
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-             }
--            else if (!check_hex (simple, n_compose))
--                /* FIXME beep_window (event->window); */
--                ;
-             ibus_engine_simple_update_preedit_text (simple);
- 
-             return TRUE;
-@@ -1343,7 +1380,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
- 
-             n_compose = n_compose_prev;
-             g_assert (n_compose < (COMPOSE_BUFFER_SIZE + 1));
--            /* FIXME beep_window (event->window); */
-+            ibus_engine_simple_send_message_with_code (
-+                    simple,
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-             backup_char = priv->compose_buffer[n_compose];
-             priv->compose_buffer[n_compose] = 0;
-             if (ibus_engine_simple_check_all_compose_table (simple, n_compose))
-diff --git a/src/ibusmessage.c b/src/ibusmessage.c
-new file mode 100644
-index 00000000..89e31771
---- /dev/null
-+++ b/src/ibusmessage.c
-@@ -0,0 +1,562 @@
-+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
-+/* vim:set et sts=4: */
-+/* bus - The Input Bus
-+ * Copyright (C) 2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2025 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 "ibusmessage.h"
-+#include "ibusinternal.h"
-+
-+enum {
-+    LAST_SIGNAL,
-+};
-+
-+enum {
-+    PROP_0 = 0,
-+    PROP_DOMAIN,
-+    PROP_CODE,
-+    PROP_TITLE,
-+    PROP_DESCRIPTION,
-+    PROP_TIMEOUT,
-+    PROP_PROGRESS,
-+    PROP_SERIAL
-+};
-+
-+
-+/* IBusMessagePriv */
-+struct _IBusMessagePrivate {
-+    guchar      domain;
-+    guchar      code;
-+    gchar      *title;
-+    gchar      *description;
-+    int         timeout;
-+    int         progress;
-+    guint       serial;
-+};
-+
-+
-+#define ibus_message_get_const_instance_private(self) \
-+    G_STRUCT_MEMBER_P ((self), IBusMessage_private_offset)
-+
-+#define IBUS_MESSAGE_GET_PRIVATE(o)  \
-+    ((IBusMessagePrivate *)ibus_message_get_instance_private (o))
-+#define IBUS_MESSAGE_GET_CONST_PRIVATE(o)  \
-+    ((const IBusMessagePrivate *)ibus_message_get_const_instance_private (o))
-+
-+#define DEFAULT_TIMEOUT -1
-+#define DEFAULT_PROGRESS -1
-+
-+// static guint            _signals[LAST_SIGNAL] = { 0 };
-+
-+/* functions prototype */
-+static void     ibus_message_set_property      (IBusMessage            *msg,
-+                                                guint                  prop_id,
-+                                                const GValue           *value,
-+                                                GParamSpec             *pspec);
-+static void     ibus_message_get_property      (IBusMessage            *msg,
-+                                                guint                   prop_id,
-+                                                GValue                 *value,
-+                                                GParamSpec             *pspec);
-+static void     ibus_message_destroy           (IBusMessage            *msg);
-+static gboolean ibus_message_serialize         (IBusMessage            *msg,
-+                                                GVariantBuilder
-+                                                                      *builder);
-+static gint     ibus_message_deserialize       (IBusMessage            *msg,
-+                                                GVariant
-+                                                                      *variant);
-+static gboolean ibus_message_copy              (IBusMessage            *dest,
-+                                                const IBusMessage      *src);
-+
-+G_DEFINE_TYPE_WITH_PRIVATE (IBusMessage,
-+                            ibus_message,
-+                            IBUS_TYPE_SERIALIZABLE)
-+
-+
-+static void
-+ibus_message_class_init (IBusMessageClass *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_message_set_property;
-+    gobject_class->get_property =
-+            (GObjectGetPropertyFunc)ibus_message_get_property;
-+    object_class->destroy = (IBusObjectDestroyFunc)ibus_message_destroy;
-+
-+    serializable_class->serialize   =
-+            (IBusSerializableSerializeFunc)ibus_message_serialize;
-+    serializable_class->deserialize =
-+            (IBusSerializableDeserializeFunc)ibus_message_deserialize;
-+    serializable_class->copy        =
-+            (IBusSerializableCopyFunc)ibus_message_copy;
-+
-+    /* install properties */
-+    /**
-+     * IBusMessage:domain:
-+     *
-+     * The domain of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_DOMAIN,
-+                    g_param_spec_uchar ("domain",
-+                        "message domain",
-+                        "The domain of message",
-+                        0,
-+                        G_MAXUINT8,
-+                        0,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:code:
-+     *
-+     * The code of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_CODE,
-+                    g_param_spec_uchar ("code",
-+                        "message code",
-+                        "The code of message",
-+                        0,
-+                        G_MAXUINT8,
-+                        0,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:title:
-+     *
-+     * The title of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_TITLE,
-+                    g_param_spec_string ("title",
-+                        "message title",
-+                        "The title of message",
-+                        NULL,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:description:
-+     *
-+     * The description of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_DESCRIPTION,
-+                    g_param_spec_string ("description",
-+                        "message description",
-+                        "The description of message",
-+                        "",
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:timeout:
-+     *
-+     * The timeout of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_TIMEOUT,
-+                    g_param_spec_int ("timeout",
-+                        "message timeout",
-+                        "The timeout of message",
-+                        G_MININT,
-+                        G_MAXINT,
-+                        DEFAULT_TIMEOUT,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:progress:
-+     *
-+     * The progress of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_PROGRESS,
-+                    g_param_spec_int ("progress",
-+                        "message progress",
-+                        "The progress of message",
-+                        G_MININT,
-+                        G_MAXINT,
-+                        DEFAULT_PROGRESS,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+
-+    /**
-+     * IBusMessage:serial:
-+     *
-+     * The serial of message
-+     */
-+    g_object_class_install_property (gobject_class,
-+                    PROP_SERIAL,
-+                    g_param_spec_uint ("serial",
-+                        "message serial",
-+                        "The serial of message",
-+                        0,
-+                        G_MAXUINT,
-+                        0,
-+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-+}
-+
-+
-+static void
-+ibus_message_init (IBusMessage *msg)
-+{
-+    IBusMessagePrivate *priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    priv->timeout = DEFAULT_TIMEOUT;
-+    priv->progress = DEFAULT_PROGRESS;
-+}
-+
-+
-+static void
-+ibus_message_destroy (IBusMessage *msg)
-+{
-+    IBusMessagePrivate *priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    g_free (priv->title);
-+    g_free (priv->description);
-+
-+    IBUS_OBJECT_CLASS (ibus_message_parent_class)->destroy (IBUS_OBJECT (msg));
-+}
-+
-+
-+static void
-+ibus_message_set_property (IBusMessage  *msg,
-+                           guint         prop_id,
-+                           const GValue *value,
-+                           GParamSpec   *pspec)
-+{
-+    IBusMessagePrivate *priv;
-+
-+    g_return_if_fail (IBUS_IS_MESSAGE (msg));
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    switch (prop_id) {
-+    case PROP_DOMAIN:
-+        g_assert (priv->domain == 0);
-+        priv->domain = g_value_get_uchar (value);
-+        break;
-+    case PROP_CODE:
-+        g_assert (priv->code == 0);
-+        priv->code = g_value_get_uchar (value);
-+        break;
-+    case PROP_TITLE:
-+        g_assert (priv->title == NULL);
-+        priv->title = g_value_dup_string (value);
-+        break;
-+    case PROP_DESCRIPTION:
-+        g_assert (priv->description == NULL);
-+        priv->description  = g_value_dup_string (value);
-+        break;
-+    case PROP_TIMEOUT:
-+        g_assert (priv->timeout == DEFAULT_TIMEOUT);
-+        priv->timeout = g_value_get_int (value);
-+        break;
-+    case PROP_PROGRESS:
-+        g_assert (priv->progress == DEFAULT_PROGRESS);
-+        priv->progress = g_value_get_int (value);
-+        break;
-+    case PROP_SERIAL:
-+        g_assert (priv->serial == 0);
-+        priv->serial = g_value_get_uint (value);
-+        break;
-+    default:
-+        G_OBJECT_WARN_INVALID_PROPERTY_ID (msg, prop_id, pspec);
-+    }
-+}
-+
-+
-+static void
-+ibus_message_get_property (IBusMessage *msg,
-+                           guint        prop_id,
-+                           GValue      *value,
-+                           GParamSpec  *pspec)
-+{
-+    switch (prop_id) {
-+    case PROP_DOMAIN: {
-+            int domain = ibus_message_get_domain (msg);
-+            g_assert (domain > 0 && domain <= G_MAXUINT8);
-+            g_value_set_uchar (value, (guchar)domain);
-+        }
-+        break;
-+    case PROP_CODE: {
-+            int code = ibus_message_get_code (msg);
-+            g_assert (code >= 0 && code <= G_MAXUINT8);
-+            g_value_set_uchar (value, (guchar)code);
-+        }
-+        break;
-+    case PROP_TITLE:
-+        g_value_set_string (value, ibus_message_get_title (msg));
-+        break;
-+    case PROP_DESCRIPTION:
-+        g_value_set_string (value, ibus_message_get_description (msg));
-+        break;
-+    case PROP_TIMEOUT:
-+        g_value_set_int (value, ibus_message_get_timeout (msg));
-+        break;
-+    case PROP_PROGRESS:
-+        g_value_set_int (value, ibus_message_get_progress (msg));
-+        break;
-+    case PROP_SERIAL:
-+        g_value_set_uint (value, ibus_message_get_serial (msg));
-+        break;
-+    default:
-+        G_OBJECT_WARN_INVALID_PROPERTY_ID (msg, prop_id, pspec);
-+    }
-+}
-+
-+
-+static gboolean
-+ibus_message_serialize (IBusMessage     *msg,
-+                        GVariantBuilder *builder)
-+{
-+    gboolean retval;
-+    IBusMessagePrivate *priv;
-+
-+    retval = IBUS_SERIALIZABLE_CLASS (ibus_message_parent_class)->
-+            serialize ((IBusSerializable *)msg, builder);
-+    g_return_val_if_fail (retval, FALSE);
-+    /* End dict iter */
-+
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (msg), FALSE);
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+
-+#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 domain, code,
-+     * title, .... */
-+    g_variant_builder_add (builder, "y", priv->domain);
-+    g_variant_builder_add (builder, "y", priv->code);
-+    g_variant_builder_add (builder, "s", NOTNULL (priv->title));
-+    g_variant_builder_add (builder, "s", NOTNULL (priv->description));
-+    g_variant_builder_add (builder, "i", priv->timeout);
-+    g_variant_builder_add (builder, "i", priv->progress);
-+    g_variant_builder_add (builder, "u", priv->serial);
-+    /* The serialized order should be kept. */
-+#undef NOTNULL
-+
-+    return TRUE;
-+}
-+
-+
-+static gint
-+ibus_message_deserialize (IBusMessage *msg,
-+                          GVariant    *variant)
-+{
-+    gint retval;
-+    IBusMessagePrivate *priv;
-+
-+    retval = IBUS_SERIALIZABLE_CLASS (ibus_message_parent_class)->
-+            deserialize ((IBusSerializable *)msg, variant);
-+    g_return_val_if_fail (retval, 0);
-+
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (msg), retval);
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+
-+    /* If you will add a new property, you can append it at the end and
-+     * you should not change the serialized order of domain, code,
-+     * title, .... */
-+    g_variant_get_child (variant, retval++, "y", &priv->domain);
-+    g_variant_get_child (variant, retval++, "y", &priv->code);
-+    ibus_g_variant_get_child_string (variant, retval++,
-+                                     &priv->title);
-+    ibus_g_variant_get_child_string (variant, retval++,
-+                                     &priv->description);
-+    g_variant_get_child (variant, retval++, "i", &priv->timeout);
-+    g_variant_get_child (variant, retval++, "i", &priv->progress);
-+    g_variant_get_child (variant, retval++, "u", &priv->serial);
-+    /* The serialized order should be kept. */
-+    return retval;
-+}
-+
-+
-+static gboolean
-+ibus_message_copy (IBusMessage       *dest,
-+                   const IBusMessage *src)
-+{
-+    gboolean retval;
-+    IBusMessagePrivate *priv_dest;
-+    const IBusMessagePrivate *priv_src;
-+
-+    retval = IBUS_SERIALIZABLE_CLASS (ibus_message_parent_class)->
-+            copy ((IBusSerializable *)dest, (IBusSerializable *)src);
-+    g_return_val_if_fail (retval, FALSE);
-+
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (dest), FALSE);
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (src), FALSE);
-+    priv_dest  = IBUS_MESSAGE_GET_PRIVATE (dest);
-+    priv_src   = IBUS_MESSAGE_GET_CONST_PRIVATE (src);
-+
-+    priv_dest->domain            = priv_src->domain;
-+    priv_dest->code              = priv_src->code;
-+    priv_dest->title             = g_strdup (priv_src->title);
-+    priv_dest->description       = g_strdup (priv_src->description);
-+    priv_dest->timeout           = priv_src->timeout;
-+    priv_dest->progress          = priv_src->progress;
-+    priv_dest->serial            = priv_src->serial;
-+    return TRUE;
-+}
-+
-+
-+guint
-+ibus_message_get_domain (IBusMessage *msg)
-+{
-+    IBusMessagePrivate *priv;
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (msg), 0);
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    return (guint)priv->domain;
-+}
-+
-+
-+guint
-+ibus_message_get_code (IBusMessage *msg)
-+{
-+    IBusMessagePrivate *priv;
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (msg), 0);
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    return (guint)priv->code;
-+}
-+
-+
-+#define IBUS_MESSAGE_GET_PROPERTY(property, return_type, defval)        \
-+return_type                                                             \
-+ibus_message_get_ ## property (IBusMessage *msg)                        \
-+{                                                                       \
-+    IBusMessagePrivate *priv;                                           \
-+    g_return_val_if_fail (IBUS_IS_MESSAGE (msg), (defval));             \
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);                              \
-+    return priv->property;                                              \
-+}
-+
-+IBUS_MESSAGE_GET_PROPERTY (title, const gchar *, NULL)
-+IBUS_MESSAGE_GET_PROPERTY (description, const gchar *, NULL)
-+IBUS_MESSAGE_GET_PROPERTY (timeout, int, DEFAULT_TIMEOUT)
-+IBUS_MESSAGE_GET_PROPERTY (progress, int, DEFAULT_PROGRESS)
-+IBUS_MESSAGE_GET_PROPERTY (serial, guint32, 0)
-+#undef IBUS_MESSAGE_GET_PROPERTY
-+
-+
-+IBusMessage *
-+ibus_message_new (guint domain,
-+                  guint code,
-+                  const gchar *title,
-+                  const gchar *description,
-+                  ...)
-+{
-+    va_list var_args;
-+    gchar *prop;
-+    gchar **names, **names_tmp;
-+    GValue *values, *values_tmp;
-+    guint n_properties = 4, i;
-+    IBusMessage *msg;
-+    IBusMessagePrivate *priv;
-+
-+    g_return_val_if_fail (domain > 0 && domain <= G_MAXUINT8, NULL);
-+    g_return_val_if_fail (code <= G_MAXUINT8, NULL);
-+    g_return_val_if_fail (description, NULL);
-+    g_return_val_if_fail (*description != '\0', NULL);
-+
-+    if (!(names = g_new0 (gchar *, n_properties))) {
-+        g_warning ("allocation error in %s", G_STRFUNC);
-+        return NULL;
-+    }
-+    if (!(values = g_new0 (GValue, n_properties))) {
-+        g_warning ("allocation error in %s", G_STRFUNC);
-+        return NULL;
-+    }
-+
-+    names[0] = "domain";
-+    g_value_init (&values[0], G_TYPE_UCHAR);
-+    g_value_set_uchar (&values[0], domain);
-+    names[1] = "code";
-+    g_value_init (&values[1], G_TYPE_UCHAR);
-+    g_value_set_uchar (&values[1], code);
-+    names[2] = "title";
-+    g_value_init (&values[2], G_TYPE_STRING);
-+    g_value_set_string (&values[2], title);
-+    names[3] = "description";
-+    g_value_init (&values[3], G_TYPE_STRING);
-+    g_value_set_string (&values[3], description);
-+
-+#define IF_PROPERTY_IS_MATCHED(property, gtype, ctype) \
-+    if (!g_strcmp0 (prop, #property)) {                                       \
-+        if (!(names_tmp = g_renew (gchar *, names, n_properties + 1))) {      \
-+            g_warning ("allocation error in %s", G_STRFUNC);                  \
-+            va_end (var_args);                                                \
-+            g_free (names);                                                   \
-+            for (i = 0; i <= n_properties; i++)                               \
-+                g_value_unset (&values[i]);                                   \
-+            g_free (values);                                                  \
-+            return NULL;                                                      \
-+        }                                                                     \
-+        names = names_tmp;                                                    \
-+        names[n_properties] = prop;                                           \
-+        if (!(values_tmp = g_renew (GValue, values, n_properties + 1))) {     \
-+            g_warning ("allocation error in %s", G_STRFUNC);                  \
-+            va_end (var_args);                                                \
-+            g_free (names);                                                   \
-+            for (i = 0; i < n_properties; i++)                               \
-+                g_value_unset (&values[i]);                                   \
-+            g_free (values);                                                  \
-+            return NULL;                                                      \
-+        }                                                                     \
-+        values = values_tmp;                                                  \
-+        memset (&values[n_properties], 0, sizeof (GValue));                   \
-+        g_value_init (&values[n_properties], (gtype));                        \
-+        g_value_set_## ctype (&values[n_properties],                          \
-+                              va_arg (var_args, g ## ctype));                 \
-+    }
-+
-+    va_start (var_args, description);
-+    while ((prop =  va_arg (var_args, gchar *))) {
-+        IF_PROPERTY_IS_MATCHED (timeout, G_TYPE_INT, int)
-+        else IF_PROPERTY_IS_MATCHED (progress, G_TYPE_INT, int)
-+        else IF_PROPERTY_IS_MATCHED (serial, G_TYPE_UINT, uint)
-+        else {
-+            g_warning ("wrong parameter %s in %s", prop, G_STRFUNC);
-+            for (i = 0; i < n_properties; i++)
-+                g_value_unset (&values[i]);
-+            g_free (values);
-+            g_free (names);
-+            va_end (var_args);
-+            return NULL;
-+        }
-+        n_properties++;
-+    }
-+    va_end (var_args);
-+
-+#undef IF_PROPERTY_IS_MATCHED
-+
-+    msg = (IBusMessage *)g_object_new_with_properties (IBUS_TYPE_MESSAGE,
-+                                                       n_properties,
-+                                                       (const gchar **)names,
-+                                                       (const GValue*)values);
-+
-+
-+    for (i = 0; i < n_properties; i++)
-+        g_value_unset (&values[i]);
-+    g_free (values);
-+    g_free (names);
-+    if (!IBUS_IS_MESSAGE (msg)) {
-+        g_warning ("msg is not IBusMessage in %s", G_STRFUNC);
-+        return NULL;
-+    }
-+    priv = IBUS_MESSAGE_GET_PRIVATE (msg);
-+    /* name is required. Other properties are set in class_init by default. */
-+    g_assert (priv->domain > 0 && priv->domain <= G_MAXUINT8);
-+    g_assert (priv->code <= G_MAXUINT8);
-+    g_assert (priv->description);
-+    g_assert (*(priv->description) != '\0');
-+
-+    return msg;
-+}
-diff --git a/src/ibusmessage.h b/src/ibusmessage.h
-new file mode 100644
-index 00000000..0ab58b80
---- /dev/null
-+++ b/src/ibusmessage.h
-@@ -0,0 +1,205 @@
-+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
-+/* vim:set et sts=4: */
-+/* bus - The Input Bus
-+ * Copyright (C) 2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2025 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_MESSAGE_H_
-+#define __IBUS_MESSAGE_H_
-+
-+/**
-+ * SECTION: ibusmessage
-+ * @short_description: Message data which is forwarded to IBus Panel.
-+ * @title: IBusMessage
-+ * @stability: Unstable
-+ *
-+ * An IBusMessage stores the message type, description, timeout. 
-+ * The message data can generated by ibus_message_new(),
-+ *
-+ * see_also: #IBusEngine, #IBuPanels
-+ *
-+ */
-+
-+#include "ibusserializable.h"
-+
-+/*
-+ * Type macros.
-+ */
-+
-+/* define GOBJECT macros */
-+#define IBUS_TYPE_MESSAGE                 \
-+    (ibus_message_get_type ())
-+#define IBUS_MESSAGE(obj)                 \
-+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_MESSAGE, IBusMessage))
-+#define IBUS_MESSAGE_CLASS(klass)         \
-+    (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_MESSAGE, IBusMessageClass))
-+#define IBUS_IS_MESSAGE(obj)              \
-+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_MESSAGE))
-+#define IBUS_IS_MESSAGE_CLASS(klass)      \
-+    (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_MESSAGE))
-+#define IBUS_MESSAGE_GET_CLASS(obj)       \
-+    (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_MESSAGE, IBusMessageClass))
-+
-+G_BEGIN_DECLS
-+
-+typedef struct _IBusMessage IBusMessage;
-+typedef struct _IBusMessagePrivate IBusMessagePrivate;
-+typedef struct _IBusMessageClass IBusMessageClass;
-+
-+/**
-+ * IBusMessage:
-+ *
-+ * Message data which is forwarded to IBus Panel.
-+ * You can get values with g_object_get_properties.
-+ *
-+ * Since: 1.5.33
-+ */
-+struct _IBusMessage {
-+    IBusSerializable parent;
-+    /* instance members */
-+
-+    /*< public >*/
-+    /*< private >*/
-+    IBusMessagePrivate *priv;
-+};
-+
-+struct _IBusMessageClass {
-+    IBusSerializableClass parent;
-+    /* class members */
-+
-+    /*< private >*/
-+    /* padding */
-+    gpointer pdummy[5];  // We can add 5 pointers without breaking the ABI.
-+};
-+
-+GType            ibus_message_get_type          (void);
-+
-+/**
-+ * ibus_message_new:
-+ * @domain: the message domain with @IBusMessageDomain likes #GError.
-+ * @code: the message code with @IBusPanelServiceMsgCode likes #GError.
-+ * @title: the message title.
-+ * @description: the message description.
-+ * @...: the NULL-terminated arguments of the properties and values.
-+ *
-+ * Creates a new #IBusMessage.
-+ * You can give additional parameters to the API. E.g.
-+ * ibus_message_new(IBUS_MESSAGE_DOMAIN_PANEL,
-+ * IBUS_PANEL_SERVICE_MSG_CODE_LOADING_UNICODE, "IBus Emoji utility",
-+ * "Loading a Unicode dictionary", "timeout", 10, "progress", 30, NULL)
-+ *
-+ * Returns: A newly allocated IBusMessage.
-+ *
-+ * Since: 1.5.33
-+ */
-+IBusMessage     *ibus_message_new               (guint           domain,
-+                                                 guint           code,
-+                                                 const gchar    *title,
-+                                                 const gchar    *description,
-+                                                 ...) G_GNUC_NULL_TERMINATED;
-+
-+/**
-+ * ibus_message_get_domain:
-+ * @msg: An #IBusMessage.
-+ *
-+ * Gets the domain property in #IBusMessage.
-+ *
-+ * Returns: domain property in #IBusMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+guint            ibus_message_get_domain        (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_code:
-+ * @msg: An #IBusMessage.
-+ *
-+ * Gets the code property in #IBusMessage.
-+ *
-+ * Returns: code property in #IBusMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+guint            ibus_message_get_code          (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_title:
-+ * @msg: An #IBuMessages.
-+ *
-+ * Gets the title property in #IBusMessage. It should not be freed.
-+ *
-+ * Returns: title property in #IBuaMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+const gchar     *ibus_message_get_title         (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_description:
-+ * @msg: An #IBuMessages.
-+ *
-+ * Gets the description property in #IBusMessage. It should not be freed.
-+ *
-+ * Returns: description property in #IBuaMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+const gchar     *ibus_message_get_description   (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_timeout:
-+ * @msg: An #IBusMessage.
-+ *
-+ * Gets the timeout property in #IBusMessage.
-+ *
-+ * Returns: timeout property in #IBusMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+int              ibus_message_get_timeout       (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_progress:
-+ * @msg: An #IBusMessage.
-+ *
-+ * Gets the progress property in #IBusMessage.
-+ *
-+ * Returns: progress property in #IBusMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+int              ibus_message_get_progress      (IBusMessage  *msg);
-+
-+/**
-+ * ibus_message_get_serial:
-+ * @msg: An #IBusMessage.
-+ *
-+ * Gets the serial property in #IBusMessage.
-+ *
-+ * Returns: serial property in #IBusMessage
-+ *
-+ * Since: 1.5.33
-+ */
-+guint            ibus_message_get_serial        (IBusMessage  *msg);
-+G_END_DECLS
-+#endif
-diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c
-index 80637ca2..9e3939d0 100644
---- a/src/ibuspanelservice.c
-+++ b/src/ibuspanelservice.c
-@@ -3,7 +3,7 @@
- /* ibus - The Input Bus
-  * Copyright (c) 2009-2014 Google Inc. All rights reserved.
-  * Copyright (C) 2010-2014 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright (C) 2017-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2017-2025 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
-@@ -60,6 +60,7 @@ enum {
-     PROCESS_KEY_EVENT,
-     COMMIT_TEXT_RECEIVED,
-     CANDIDATE_CLICKED_LOOKUP_TABLE,
-+    SEND_MESSAGE_RECEIVED,
-     LAST_SIGNAL,
- };
- 
-@@ -240,6 +241,13 @@ static const gchar introspection_xml[] =
-     "    <method name='CommitTextReceived'>"
-     "      <arg direction='in' type='v' name='text' />"
-     "    </method>"
-+    "    <method name='SendMessageReceived'>"
-+    "      <arg direction='in' type='v' name='message' />"
-+    "      <annotation name='org.gtk.GDBus.Since'\n"
-+    "          value='1.5.33' />\n"
-+    "      <annotation name='org.gtk.GDBus.DocString'\n"
-+    "          value='Stability: Unstable' />\n"
-+    "    </method>"
-     /* Signals */
-     "    <signal name='CursorUp' />"
-     "    <signal name='CursorDown' />"
-@@ -266,9 +274,9 @@ static const gchar introspection_xml[] =
-     "    <signal name='PanelExtension'>"
-     "      <arg type='v' name='event' />"
-     "    </signal>"
--    "    <method name='PanelExtensionRegisterKeys'>"
-+    "    <signal name='PanelExtensionRegisterKeys'>"
-     "      <arg type='v' name='data' />"
--    "    </method>"
-+    "    </signal>"
-     "    <signal name='UpdatePreeditTextReceived'>"
-     "      <arg type='v' name='text' />"
-     "      <arg type='u' name='cursor_pos' />"
-@@ -286,6 +294,17 @@ static const gchar introspection_xml[] =
-     "      <arg type='u' name='keyval' />"
-     "      <arg type='u' name='keycode' />"
-     "      <arg type='u' name='state' />"
-+    "      <annotation name='org.gtk.GDBus.Since'\n"
-+    "          value='1.5.32' />\n"
-+    "      <annotation name='org.gtk.GDBus.DocString'\n"
-+    "          value='Stability: Unstable' />\n"
-+    "    </signal>"
-+    "    <signal name='SendMessage'>"
-+    "      <arg type='v' name='message' />"
-+    "      <annotation name='org.gtk.GDBus.Since'\n"
-+    "          value='1.5.33' />\n"
-+    "      <annotation name='org.gtk.GDBus.DocString'\n"
-+    "          value='Stability: Unstable' />\n"
-     "    </signal>"
-     "  </interface>"
-     "</node>";
-@@ -1006,7 +1025,7 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
-     /**
-      * IBusPanelService::commit-text-received:
-      * @panel: An #IBusPanelService
--     * @text: A #IBusText
-+     * @text: An #IBusText
-      *
-      * Emitted when the client application get the ::commit-text-received.
-      * Implement the member function
-@@ -1027,6 +1046,20 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
-             1,
-             IBUS_TYPE_TEXT);
- 
-+    /**
-+     * IBusPanelService::candidate-clicked-lookup-table:
-+     * @panel: An #IBusPanelService
-+     * @text: An #IBusText
-+     *
-+     * Emitted when the client application get the
-+     * ::candidate-clicked-lookup-table.
-+     * Implement the member function
-+     * IBusPanelServiceClass::candidate_cllicked_lookup_table in extended class
-+     * to receive this signal.
-+     *
-+     * <note><para>Argument @user_data is ignored in this function.</para>
-+     * </note>
-+     */
-     panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE] =
-         g_signal_new (I_("candidate-clicked-lookup-table"),
-             G_TYPE_FROM_CLASS (gobject_class),
-@@ -1040,6 +1073,31 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class)
-             G_TYPE_UINT,
-             G_TYPE_UINT,
-             G_TYPE_UINT);
-+
-+    /**
-+     * IBusPanelService::send-message-received:
-+     * @panel: An #IBusPanelService
-+     * @message: An #IBusMessage
-+     *
-+     * Emitted when the client application get the ::send-meeeage-received.
-+     * Implement the member function
-+     *
-+     * <note><para>Argument @user_data is ignored in this function.</para>
-+     * </note>
-+     *
-+     * Since: 1.5.33
-+     * Stability: Unstable
-+     */
-+    panel_signals[SEND_MESSAGE_RECEIVED] =
-+        g_signal_new (I_("send-message-received"),
-+            G_TYPE_FROM_CLASS (gobject_class),
-+            G_SIGNAL_RUN_LAST,
-+            0,
-+            NULL, NULL,
-+            _ibus_marshal_VOID__OBJECT,
-+            G_TYPE_NONE,
-+            1,
-+            IBUS_TYPE_MESSAGE);
- }
- 
- static void
-@@ -1310,6 +1368,26 @@ ibus_panel_service_service_method_call (IBusService           *service,
-                        index, button, state);
-         return;
-     }
-+    if (g_strcmp0 (method_name, "SendMessageReceived") == 0) {
-+        GVariant *arg0 = NULL;
-+        IBusMessage *message = NULL;
-+        g_variant_get (parameters, "(v)", &arg0);
-+        if (arg0) {
-+            message = (IBusMessage *)ibus_serializable_deserialize (arg0);
-+            g_variant_unref (arg0);
-+        }
-+        if (!message) {
-+            g_dbus_method_invocation_return_error (
-+                    invocation,
-+                    G_DBUS_ERROR,
-+                    G_DBUS_ERROR_FAILED,
-+                    "SendMessageReceived method gives NULL");
-+            return;
-+        }
-+        g_signal_emit (panel, panel_signals[SEND_MESSAGE_RECEIVED], 0, message);
-+        _g_object_unref_if_floating (message);
-+        return;
-+    }
- 
- 
-     const static struct {
-@@ -1510,7 +1588,7 @@ ibus_panel_service_candidate_clicked (IBusPanelService *panel,
-                                       guint             state)
- {
-     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "CandidateClicked",
-@@ -1524,7 +1602,7 @@ ibus_panel_service_property_activate (IBusPanelService *panel,
-                                       guint             prop_state)
- {
-     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "PropertyActivate",
-@@ -1537,7 +1615,7 @@ ibus_panel_service_property_show (IBusPanelService *panel,
-                                   const gchar      *prop_name)
- {
-     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "PropertyShow",
-@@ -1550,7 +1628,7 @@ ibus_panel_service_property_hide (IBusPanelService *panel,
-                                   const gchar      *prop_name)
- {
-     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "PropertyHide",
-@@ -1567,7 +1645,7 @@ ibus_panel_service_commit_text (IBusPanelService *panel,
-     g_return_if_fail (IBUS_IS_TEXT (text));
- 
-     variant = ibus_serializable_serialize ((IBusSerializable *)text);
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "CommitText",
-@@ -1588,7 +1666,7 @@ ibus_panel_service_panel_extension (IBusPanelService   *panel,
-     g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event));
- 
-     variant = ibus_serializable_serialize ((IBusSerializable *)event);
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "PanelExtension",
-@@ -1662,7 +1740,7 @@ ibus_panel_service_update_preedit_text_received (IBusPanelService *panel,
- 
-     variant = ibus_serializable_serialize ((IBusSerializable *)text);
-     g_return_if_fail (variant);
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "UpdatePreeditTextReceived",
-@@ -1686,7 +1764,7 @@ ibus_panel_service_update_auxiliary_text_received (IBusPanelService *panel,
- 
-     variant = ibus_serializable_serialize ((IBusSerializable *)text);
-     g_return_if_fail (variant);
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "UpdateAuxiliaryTextReceived",
-@@ -1711,7 +1789,7 @@ ibus_panel_service_update_lookup_table_received (IBusPanelService *panel,
- 
-     variant = ibus_serializable_serialize ((IBusSerializable *)table);
-     g_return_if_fail (variant);
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "UpdateLookupTableReceived",
-@@ -1731,7 +1809,7 @@ ibus_panel_service_forward_process_key_event (IBusPanelService *panel,
-                                               guint32           state)
- {
-     g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
--    ibus_service_emit_signal ((IBusService *) panel,
-+    ibus_service_emit_signal ((IBusService *)panel,
-                               NULL,
-                               IBUS_INTERFACE_PANEL,
-                               "ForwardProcessKeyEvent",
-@@ -1740,6 +1818,28 @@ ibus_panel_service_forward_process_key_event (IBusPanelService *panel,
-                               NULL);
- }
- 
-+void
-+ibus_panel_service_send_message (IBusPanelService *panel,
-+                                 IBusMessage      *message)
-+{
-+    GVariant *variant;
-+    GError *error = NULL;
-+
-+    g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel));
-+    g_return_if_fail (IBUS_IS_MESSAGE (message));
-+    variant = ibus_serializable_serialize ((IBusSerializable *)message);
-+    ibus_service_emit_signal ((IBusService *)panel,
-+                              NULL,
-+                              IBUS_INTERFACE_PANEL,
-+                              "SendMessage",
-+                              g_variant_new ("(v)", variant),
-+                              &error);
-+    if (error) {
-+        g_warning ("Error in %s: %s", G_STRFUNC, error->message);
-+        g_error_free (error);
-+    }
-+}
-+
- #define DEFINE_FUNC(name, Name)                             \
-     void                                                    \
-     ibus_panel_service_##name (IBusPanelService *panel)     \
-diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h
-index 64efca55..57045956 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-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (c) 2017-2025 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
-@@ -36,6 +36,7 @@
-  * Developers can "extend" this class for panel UI development.
-  */
- #include "ibuslookuptable.h"
-+#include "ibusmessage.h"
- #include "ibusservice.h"
- #include "ibusproplist.h"
- #include "ibusxevent.h"
-@@ -60,6 +61,24 @@
- 
- G_BEGIN_DECLS
- 
-+/**
-+ * IBusPanelServiceMsgCode:
-+ * @IBUS_PANEL_SERVICE_MSG_CODE_GENERAL: Generic message for Panel
-+ * @IBUS_PANEL_SERVICE_MSG_CODE_LOADING_UNICODE: Progress message when the
-+ *         Unicode data is loading.
-+ *
-+ * Message codes in the `IBusMessageDomain` domain for Panel.
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+typedef enum
-+{
-+  IBUS_PANEL_SERVICE_MSG_CODE_GENERAL,
-+  IBUS_PANEL_SERVICE_MSG_CODE_LOADING_UNICODE
-+} IBusPanelServiceMsgCode;
-+
-+
- typedef struct _IBusPanelService IBusPanelService;
- typedef struct _IBusPanelServiceClass IBusPanelServiceClass;
- 
-@@ -372,11 +391,28 @@ void ibus_panel_service_update_lookup_table_received
-  *
-  * Forward key events when an IBus popup takes the focus and the events
-  * needs to be forwared to the target IBus engine.
-+ *
-+ * Since: 1.5.32
-+ * Stability: Unstable
-  */
- void ibus_panel_service_forward_process_key_event
-                                           (IBusPanelService *panel,
-                                            guint32           keyval,
-                                            guint32           keycode,
-                                            guint32           state);
-+
-+/**
-+ * ibus_panel_service_send_message:
-+ * @panel: An #IBusPanelService.
-+ * @message: An #IBusMessage.
-+ *
-+ * Send a message to the Panel for the focus-less notification popup.
-+ * This is used for the emoji component in Wayland mainly but in Xorg too.
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+void ibus_panel_service_send_message      (IBusPanelService *panel,
-+                                           IBusMessage      *message);
- G_END_DECLS
- #endif
-diff --git a/src/ibustypes.h b/src/ibustypes.h
-index c8dfb89a..1243b2bc 100644
---- a/src/ibustypes.h
-+++ b/src/ibustypes.h
-@@ -358,5 +358,19 @@ typedef enum
-         IBUS_SUPER_MASK |                       \
-         IBUS_HYPER_MASK |                       \
-         IBUS_META_MASK))
-+/**
-+ * IBusMessageDomain:
-+ * @IBUS_MESSAGE_ENGINE: The message domain for Engine messages
-+ * @IBUS_MESSAGE_PANEL: The message domain for Panel messages
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+typedef enum
-+{
-+  IBUS_MESSAGE_DOMAIN_NONE,
-+  IBUS_MESSAGE_DOMAIN_ENGINE,
-+  IBUS_MESSAGE_DOMAIN_PANEL
-+} IBusMessageDomain;
- 
- #endif
-diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
-index 4cc10780..d99d8e82 100644
---- a/ui/gtk3/Makefile.am
-+++ b/ui/gtk3/Makefile.am
-@@ -97,6 +97,7 @@ ibus_ui_gtk3_SOURCES = \
-     handle.vala \
-     iconwidget.vala \
-     keybindingmanager.vala \
-+    message.vala \
-     panel.vala \
-     pango.vala \
-     property.vala \
-diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 1770de2f..2d8754a4 100644
---- a/ui/gtk3/emojier.vala
-+++ b/ui/gtk3/emojier.vala
-@@ -2,7 +2,7 @@
-  *
-  * ibus - The Input Bus
-  *
-- * Copyright (c) 2017-2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (c) 2017-2025 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
-@@ -312,6 +312,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     private uint m_unicode_progress_id;
-     private Gtk.Label m_unicode_percent_label;
-     private double m_unicode_percent;
-+    private ulong m_unicode_deserialize_unicode_signal_id;
-     private Gdk.Rectangle m_cursor_location;
-     private bool m_is_up_side_down = false;
-     private uint m_redraw_window_id;
-@@ -321,6 +322,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     public signal void candidate_clicked(uint index, uint button, uint state);
-     public signal void commit_text(string text);
-     public signal void cancel();
-+    public signal void send_message(IBus.Message message);
- 
-     public IBusEmojier(bool is_wayland) {
-         GLib.Object(
-@@ -1178,9 +1180,13 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         hbox.pack_start(m_unicode_percent_label, false, true, 0);
-         hbox.show_all();
- 
--        m_unicode_progress_object.deserialize_unicode.connect((i, n) => {
--            m_unicode_percent = (double)i / n;
--        });
-+        if (m_unicode_deserialize_unicode_signal_id == 0) {
-+            m_unicode_deserialize_unicode_signal_id =
-+                    m_unicode_progress_object.deserialize_unicode.connect(
-+                            (i, n) => {
-+                                m_unicode_percent = (double)i / n;
-+                    });
-+        }
-         if (m_unicode_progress_id > 0) {
-             GLib.Source.remove(m_unicode_progress_id);
-         }
-@@ -1199,6 +1205,18 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     }
- 
- 
-+    private void show_unicode_popup(int progress) {
-+        var message = new IBus.Message(IBus.MessageDomain.PANEL,
-+                                       IBus.PanelServiceMsgCode.LOADING_UNICODE,
-+                                       _("IBus Emoji initialization"),
-+                                       _("Loading a Unicode dictionary:"),
-+                                       "timeout", 3,
-+                                       "progress", progress,
-+                                       "serial", 50001);
-+        send_message(message);
-+    }
-+
-+
-     private static string? check_unicode_point(string annotation) {
-         string unicode_point = null;
-         // Add "0x" because uint64.ascii_strtoull() is not accessible
-@@ -1232,7 +1250,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     }
- 
- 
--    private static GLib.SList<string>?
-+    private GLib.SList<string>?
-     lookup_emojis_from_annotation(string annotation) {
-         GLib.SList<string>? total_emojis = null;
-         unowned GLib.SList<string>? sub_emojis = null;
-@@ -1310,6 +1328,17 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-                 }
-             }
-         }
-+        if (!m_loaded_unicode && m_unicode_deserialize_unicode_signal_id == 0) {
-+            m_unicode_deserialize_unicode_signal_id =
-+                    m_unicode_progress_object.deserialize_unicode.connect(
-+                            (i, n) => {
-+                                m_unicode_percent = (double)i / n;
-+                                show_unicode_popup(
-+                                        (int)(m_unicode_percent * 100));
-+                    });
-+        }
-+        if (!m_loaded_unicode)
-+            show_unicode_popup(0);
-         return total_emojis;
-     }
- 
-diff --git a/ui/gtk3/message.vala b/ui/gtk3/message.vala
-new file mode 100644
-index 00000000..0c7dd478
---- /dev/null
-+++ b/ui/gtk3/message.vala
-@@ -0,0 +1,353 @@
-+/* vim:set et sts=4 sw=4:
-+ *
-+ * ibus - The Input Bus
-+ *
-+ * Copyright(c) 2025 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
-+ */
-+
-+public class MessageDialog : Gtk.Box{
-+    private Gtk.Window m_toplevel;
-+    private Gtk.Box m_vbox;
-+    private Gtk.Box m_hbox;
-+    private Gtk.Label m_title_label;
-+    private Gtk.Label m_description_label;
-+    private Gtk.ProgressBar m_progress_bar;
-+    private Gtk.Label m_progress_label;
-+    private Gtk.Label m_timeout_label;
-+    private Gtk.Button m_button;
-+    private int m_timeout_time;
-+    private int m_elapsed_time;
-+    private uint m_timeout_id;
-+
-+    private Gdk.Rectangle m_cursor_location;
-+
-+    private bool m_is_wayland;
-+    private bool m_no_wayland_panel;
-+
-+#if USE_GDK_WAYLAND
-+    private bool m_hide_after_show;
-+#endif
-+
-+    public signal void close();
-+
-+#if USE_GDK_WAYLAND
-+    public signal void realize_surface(void *surface);
-+#endif
-+
-+    public MessageDialog (bool         is_wayland,
-+                          bool         no_wayland_panel,
-+                          IBus.Message message) {
-+        // Call base class constructor
-+        GLib.Object(
-+            name : "IBusMessageDialog",
-+            orientation: Gtk.Orientation.HORIZONTAL,
-+            visible: true
-+        );
-+
-+        m_is_wayland = is_wayland;
-+        m_no_wayland_panel = no_wayland_panel;
-+        m_toplevel = new Gtk.Window(Gtk.WindowType.POPUP);
-+        m_toplevel.add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
-+        m_toplevel.size_allocate.connect((w, a) => {
-+            adjust_window_position(w);
-+        });
-+#if USE_GDK_WAYLAND
-+        if (m_is_wayland) {
-+            m_toplevel.realize.connect((w) => {
-+                realize_window(true);
-+            });
-+            m_toplevel.show.connect((w) => {
-+                if (m_hide_after_show)
-+                    realize_window(false);
-+                m_hide_after_show = false;
-+            });
-+            m_toplevel.hide.connect((w) => {
-+                m_hide_after_show = true;
-+            });
-+        }
-+#endif
-+
-+        m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-+        m_vbox.set_visible(true);
-+        pack_start(m_vbox, false, false, 0);
-+
-+        m_toplevel.add(this);
-+
-+        create_ui(message);
-+    }
-+
-+    public void set_cursor_location(int x, int y, int width, int height) {
-+        Gdk.Rectangle location = Gdk.Rectangle(){
-+            x = x, y = y, width = width, height = height };
-+        if (m_cursor_location == location)
-+            return;
-+        m_cursor_location = location;
-+
-+        /* Do not call adjust_window_position() here because
-+         * m_toplevel is not shown yet and
-+         * m_toplevel.get_allocation() returns height = width = 1 */
-+    }
-+
-+    private void create_ui(IBus.Message message) {
-+        string? title = message.get_title();
-+        string description = message.get_description();
-+
-+        if (title.length > 0) {
-+            m_title_label = new Gtk.Label(title);
-+        } else {
-+            m_title_label = new Gtk.Label(null);
-+            m_title_label.set_no_show_all(true);
-+        }
-+        m_title_label.set_halign(Gtk.Align.CENTER);
-+        m_title_label.set_valign(Gtk.Align.CENTER);
-+        m_title_label.set_margin_start(8);
-+        m_title_label.set_margin_end(8);
-+        m_title_label.set_line_wrap(true);
-+        m_title_label.set_width_chars(50);
-+
-+        m_description_label = new Gtk.Label(description);
-+        m_description_label.set_halign(Gtk.Align.START);
-+        m_description_label.set_valign(Gtk.Align.CENTER);
-+        m_description_label.set_margin_start(8);
-+        m_description_label.set_margin_end(8);
-+        m_description_label.set_line_wrap(true);
-+        m_description_label.set_width_chars(50);
-+
-+        m_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
-+        m_hbox.set_halign(Gtk.Align.CENTER);
-+        m_hbox.set_valign(Gtk.Align.CENTER);
-+        m_progress_bar = new Gtk.ProgressBar();
-+        m_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
-+        m_progress_bar.set_halign(Gtk.Align.CENTER);
-+        m_progress_bar.set_valign(Gtk.Align.CENTER);
-+        m_progress_label = new Gtk.Label(null);
-+
-+        int progress = message.get_progress();
-+        double progress_f = progress / 100.0;
-+        if (progress_f <= 1.0 && progress >= 0) {
-+            m_progress_bar.set_fraction(progress_f);
-+            m_progress_bar.set_text(
-+                    "%2u%%\n".printf(progress));
-+            m_progress_label.set_text(
-+                    "%2u%%\n".printf(progress));
-+        } else {
-+            if (progress_f > 1.0)
-+                warning ("Progress should be below 100.");
-+            m_hbox.set_no_show_all(true);
-+        }
-+
-+        m_timeout_time = message.get_timeout();
-+        if (m_timeout_time >= 0) {
-+            m_timeout_label = new Gtk.Label(_("%5dSec").printf(m_timeout_time));
-+        } else {
-+            m_timeout_label = new Gtk.Label(null);
-+            m_timeout_label.set_no_show_all(true);
-+        }
-+        m_timeout_label.set_size_request(20, -1);
-+        m_timeout_label.set_halign(Gtk.Align.CENTER);
-+        m_timeout_label.set_valign(Gtk.Align.CENTER);
-+        m_timeout_label.set_margin_start(8);
-+        m_timeout_label.set_margin_end(8);
-+
-+        if (message.get_serial() > 0 && m_timeout_time >= 0) {
-+            m_button = new Gtk.Button();
-+            m_button.set_no_show_all(true);
-+        } else {
-+            m_button = new Gtk.Button.with_label(_("Close"));
-+            m_button.get_style_context().add_class("text-button");
-+            m_button.set_can_default(true);
-+        }
-+        m_button.set_use_underline(true);
-+        m_button.set_valign(Gtk.Align.BASELINE);
-+        m_button.clicked.connect((w) => {
-+            if (m_timeout_id > 0) {
-+                GLib.Source.remove(m_timeout_id);
-+                m_timeout_id = 0;
-+            }
-+            close();
-+        });
-+        if (m_timeout_time >= 0) {
-+            m_elapsed_time = 1;
-+            m_timeout_id = Timeout.add_seconds(1,
-+                    () => {
-+                        if (m_elapsed_time >= m_timeout_time) {
-+                            close();
-+                            m_timeout_id = 0;
-+                            return Source.REMOVE;
-+                        }
-+                        m_timeout_label.set_text(_("%5dSec").printf(
-+                                m_timeout_time - m_elapsed_time++));
-+                        return Source.CONTINUE;
-+                    });
-+        }
-+
-+        m_vbox.pack_start(m_title_label, false, false, 4);
-+        m_vbox.pack_start(m_description_label, false, false, 4);
-+        m_vbox.add(m_hbox);
-+        m_hbox.add(m_progress_bar);
-+        m_hbox.add(m_progress_label);
-+        m_vbox.pack_start(m_timeout_label, false, false, 4);
-+        m_vbox.pack_start(m_button, false, false, 0);
-+
-+        m_toplevel.resize(1, 1);
-+    }
-+
-+    public void update_message(IBus.Message message) {
-+        string? title = message.get_title();
-+        string description = message.get_description();
-+
-+        if (title.length > 0) {
-+            m_title_label.set_text(title);
-+            m_title_label.show();
-+        } else {
-+            m_title_label.hide();
-+        }
-+        m_description_label.set_text(description);
-+
-+        m_timeout_time = message.get_timeout();
-+        if (m_timeout_time >= 0) {
-+            m_timeout_label.set_text(_("%5dSec").printf(m_timeout_time));
-+            m_timeout_label.show();
-+            m_elapsed_time = 1;
-+        } else {
-+            m_timeout_label.hide();
-+        }
-+
-+        int progress = message.get_progress();
-+        double progress_f = progress / 100.0;
-+        if (progress_f <= 1.0 && progress >= 0) {
-+            m_progress_bar.set_fraction(progress_f);
-+            m_progress_bar.set_text(
-+                    "%2u%%\n".printf(progress));
-+            m_progress_label.set_text(
-+                    "%2u%%\n".printf(progress));
-+            m_hbox.show();
-+        } else {
-+            if (progress_f > 1.0)
-+                warning ("Progress should be below 100.");
-+            m_hbox.hide();
-+        }
-+
-+        m_toplevel.resize(1, 1);
-+        this.show();
-+    }
-+
-+    public new void show() {
-+        m_toplevel.show_all();
-+    }
-+
-+    public new void hide() {
-+#if USE_GDK_WAYLAND
-+        if (m_is_wayland)
-+            realize_surface(null);
-+#endif
-+        m_toplevel.hide();
-+    }
-+
-+    /**
-+     * move:
-+     * @x: left position of the #MessageDialog
-+     * @y: top position of the #MessageDialog
-+     */
-+    private void move(int x, int y) {
-+        m_toplevel.move(x, y);
-+    }
-+
-+    private void adjust_window_position(Gtk.Widget window) {
-+        adjust_window_position_horizontal(window);
-+    }
-+
-+    /**
-+     * adjust_window_position_horizontal:
-+     * @window: A Gtk.Widget of the toplevel window.
-+     *
-+     * Horizontal writing mode but not the horizontal lookup table
-+     * when the allocation is emmitted.
-+     */
-+    private void adjust_window_position_horizontal(Gtk.Widget window) {
-+        Gdk.Point cursor_left_bottom = {
-+                m_cursor_location.x,
-+                m_cursor_location.y + m_cursor_location.height
-+        };
-+
-+        Gtk.Allocation allocation;
-+        m_toplevel.get_allocation(out allocation);
-+        Gdk.Point window_right_bottom = {
-+            cursor_left_bottom.x + allocation.width,
-+            cursor_left_bottom.y + allocation.height
-+        };
-+
-+        Gdk.Rectangle monitor_area = get_monitor_geometry(window);
-+        int monitor_right = monitor_area.x + monitor_area.width;
-+        int monitor_bottom = monitor_area.y + monitor_area.height;
-+
-+        int x, y;
-+        if (window_right_bottom.x > monitor_right)
-+            x = monitor_right - allocation.width;
-+        else
-+            x = cursor_left_bottom.x;
-+        if (x < 0)
-+            x = 0;
-+
-+        if (window_right_bottom.y > monitor_bottom)
-+            y = m_cursor_location.y - allocation.height;
-+        else
-+            y = cursor_left_bottom.y;
-+        if (y < 0)
-+            y = 0;
-+
-+        move(x, y);
-+    }
-+
-+    private Gdk.Rectangle get_monitor_geometry(Gtk.Widget window) {
-+        Gdk.Rectangle monitor_area = { 0, };
-+
-+        // Use get_monitor_geometry() instead of get_monitor_area().
-+        // get_monitor_area() excludes docks, but the lookup window should be
-+        // shown over them.
-+        Gdk.Monitor monitor = window.get_display().get_monitor_at_point(
-+                m_cursor_location.x,
-+                m_cursor_location.y);
-+        monitor_area = monitor.get_geometry();
-+        return monitor_area;
-+    }
-+
-+#if USE_GDK_WAYLAND
-+    private void realize_window(bool initial) {
-+        // The custom surface can be used when the Wayland input-method
-+        // is activated.
-+        if (m_no_wayland_panel)
-+            return;
-+        var window = m_toplevel.get_window();
-+        if (!window.ensure_native()) {
-+            warning("No native window.");
-+            return;
-+        }
-+        Type instance_type = window.get_type();
-+        Type wayland_type = typeof(GdkWayland.Window);
-+        if (!instance_type.is_a(wayland_type)) {
-+            warning("Not GdkWindowWayland.");
-+            return;
-+        }
-+        if (initial)
-+            ((GdkWayland.Window)window).set_use_custom_surface();
-+        var surface = ((GdkWayland.Window)window).get_wl_surface();
-+        realize_surface(surface);
-+    }
-+#endif
-+}
-diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
-index 363834e9..d65ffbb3 100644
---- a/ui/gtk3/panel.vala
-+++ b/ui/gtk3/panel.vala
-@@ -93,6 +93,10 @@ class Panel : IBus.PanelService {
-     private ulong m_registered_status_notifier_item_id;
-     private unowned FileStream m_log;
-     private bool m_verbose;
-+    private GLib.HashTable<uint, MessageDialog> m_popup_dialogs =
-+            new GLib.HashTable<uint, MessageDialog>(GLib.direct_hash,
-+                                                    GLib.direct_equal);
-+    private uint m_popup_dialog_index;
- 
-     private GLib.List<BindingCommon.Keybinding> m_keybindings =
-             new GLib.List<BindingCommon.Keybinding>();
-@@ -246,12 +250,12 @@ class Panel : IBus.PanelService {
-         m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
- 
-         m_settings_general.changed["preload-engines"].connect((key) => {
--                update_engines(m_settings_general.get_strv(key),
--                               null);
-+            update_engines(m_settings_general.get_strv(key),
-+                           null);
-         });
- 
-         m_settings_general.changed["switcher-delay-time"].connect((key) => {
--                set_switcher_delay_time();
-+            set_switcher_delay_time();
-         });
- 
-         m_settings_general.changed["use-system-keyboard-layout"].connect(
-@@ -260,72 +264,72 @@ class Panel : IBus.PanelService {
-         });
- 
-         m_settings_general.changed["embed-preedit-text"].connect((key) => {
--                set_embed_preedit_text();
-+            set_embed_preedit_text();
-         });
- 
-         m_settings_general.changed["use-global-engine"].connect((key) => {
--                set_use_global_engine();
-+            set_use_global_engine();
-         });
- 
-         m_settings_general.changed["use-xmodmap"].connect((key) => {
--                set_use_xmodmap();
-+            set_use_xmodmap();
-         });
- 
-         m_settings_hotkey.changed["triggers"].connect((key) => {
--                BindingCommon.unbind_switch_shortcut(
--                        BindingCommon.KeyEventFuncType.IME_SWITCHER,
--                        m_keybindings);
--                m_keybindings = null;
--                bind_switch_shortcut();
-+            BindingCommon.unbind_switch_shortcut(
-+                    BindingCommon.KeyEventFuncType.IME_SWITCHER,
-+                    m_keybindings);
-+            m_keybindings = null;
-+            bind_switch_shortcut();
-         });
- 
-         m_settings_panel.changed["custom-font"].connect((key) => {
--                BindingCommon.set_custom_font(m_settings_panel,
--                                              null,
--                                              ref m_css_provider);
-+            BindingCommon.set_custom_font(m_settings_panel,
-+                                          null,
-+                                          ref m_css_provider);
-         });
- 
-         m_settings_panel.changed["use-custom-font"].connect((key) => {
--                BindingCommon.set_custom_font(m_settings_panel,
--                                              null,
--                                              ref m_css_provider);
-+            BindingCommon.set_custom_font(m_settings_panel,
-+                                          null,
-+                                          ref m_css_provider);
-         });
- 
-         m_settings_panel.changed["custom-theme"].connect((key) => {
--                BindingCommon.set_custom_theme(m_settings_panel);
-+            BindingCommon.set_custom_theme(m_settings_panel);
-         });
- 
-         m_settings_panel.changed["use-custom-theme"].connect((key) => {
--                BindingCommon.set_custom_theme(m_settings_panel);
-+            BindingCommon.set_custom_theme(m_settings_panel);
-         });
- 
-         m_settings_panel.changed["custom-icon"].connect((key) => {
--                BindingCommon.set_custom_icon(m_settings_panel);
-+            BindingCommon.set_custom_icon(m_settings_panel);
-         });
- 
-         m_settings_panel.changed["use-custom-icon"].connect((key) => {
--                BindingCommon.set_custom_icon(m_settings_panel);
-+            BindingCommon.set_custom_icon(m_settings_panel);
-         });
- 
-         m_settings_panel.changed["use-glyph-from-engine-lang"].connect((key) =>
-         {
--                set_use_glyph_from_engine_lang();
-+            set_use_glyph_from_engine_lang();
-         });
- 
-         m_settings_panel.changed["show-icon-on-systray"].connect((key) => {
--                set_show_icon_on_systray(true);
-+            set_show_icon_on_systray(true);
-         });
- 
-         m_settings_panel.changed["lookup-table-orientation"].connect((key) => {
--                set_lookup_table_orientation();
-+            set_lookup_table_orientation();
-         });
- 
-         m_settings_panel.changed["show"].connect((key) => {
--                set_show_property_panel();
-+            set_show_property_panel();
-         });
- 
-         m_settings_panel.changed["timeout"].connect((key) => {
--                set_timeout_property_panel();
-+            set_timeout_property_panel();
-         });
- 
-         m_settings_panel.changed["follow-input-cursor-when-always-shown"]
-@@ -334,11 +338,27 @@ class Panel : IBus.PanelService {
-         });
- 
-         m_settings_panel.changed["xkb-icon-rgba"].connect((key) => {
--                set_xkb_icon_rgba();
-+            set_xkb_icon_rgba();
-         });
- 
-         m_settings_panel.changed["property-icon-delay-time"].connect((key) => {
--                set_property_icon_delay_time();
-+            set_property_icon_delay_time();
-+        });
-+
-+        this.send_message_received.connect((o, m) => {
-+            uint domain = m.get_domain();
-+            switch (domain) {
-+            case IBus.MessageDomain.ENGINE:
-+                set_message_engine(m);
-+                break;
-+            case IBus.MessageDomain.PANEL:
-+                set_message_panel(m);
-+                break;
-+            default:
-+                warning("SendMessageReceived does not support the domain %u",
-+                        domain);
-+                break;
-+            }
-         });
-     }
- 
-@@ -855,6 +875,67 @@ class Panel : IBus.PanelService {
-     }
- 
- 
-+    private void set_message_engine(IBus.Message message) {
-+        uint code = message.get_code();
-+        switch (code) {
-+        case IBus.EngineMsgCode.INVALID_COMPOSE_SEQUENCE:
-+            set_message_engine_invalid_compose(message);
-+            break;
-+        default:
-+            set_message_general(message);
-+            break;
-+        }
-+    }
-+
-+
-+    private void set_message_engine_invalid_compose(IBus.Message message) {
-+        var display = Gdk.Display.get_default();
-+        display.beep();
-+    }
-+
-+
-+    private void set_message_panel(IBus.Message message) {
-+        uint code = message.get_code();
-+        switch (code) {
-+        case IBus.PanelServiceMsgCode.LOADING_UNICODE:
-+            set_message_general(message);
-+            break;
-+        }
-+    }
-+
-+
-+    private void set_message_general(IBus.Message message) {
-+        MessageDialog popup = null;
-+        uint serial = message.get_serial();
-+        if (serial != 0)
-+            popup = m_popup_dialogs.lookup(serial);
-+        else
-+            serial = m_popup_dialog_index++ + 20000;
-+        if (popup != null) {
-+            popup.update_message(message);
-+        } else {
-+            popup = new MessageDialog(m_is_wayland,
-+#if USE_GDK_WAYLAND
-+                                      m_wayland_object_path == null,
-+#else
-+                                      true,
-+#endif
-+                                      message);
-+            popup.close.connect((w) => {
-+                popup.hide();
-+                m_popup_dialogs.remove(serial);
-+                popup = null;
-+            });
-+#if USE_GDK_WAYLAND
-+            popup.realize_surface.connect(
-+                    (w, s) => this.realize_surface(s));
-+#endif
-+            m_popup_dialogs.insert(serial, popup);
-+            popup.show();
-+        }
-+    }
-+
-+
-     private int compare_versions(string version1, string version2) {
-         string[] version1_list = version1.split(".");
-         string[] version2_list = version2.split(".");
-diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
-index 10d09d75..f0827b3e 100644
---- a/ui/gtk3/panelbinding.vala
-+++ b/ui/gtk3/panelbinding.vala
-@@ -3,7 +3,7 @@
-  * ibus - The Input Bus
-  *
-  * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright(c) 2018-2024 Takao Fujwiara <takao.fujiwara1@gmail.com>
-+ * Copyright(c) 2018-2025 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
-@@ -902,6 +902,9 @@ class PanelBinding : IBus.PanelService {
-                         "is-extension", true);
-                 panel_extension(close_event);
-             });
-+            m_emojier.send_message.connect((m) => {
-+                send_message(m);
-+            });
-         }
-         m_emojier.reset();
-         m_emojier.set_annotation("");
--- 
-2.49.0
-
-From 40fcbebb8a4a60172d80482e35dcab2f39b80c2d Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Sun, 1 Jun 2025 20:32:05 +0900
-Subject: [PATCH 3/3] Add workaround until GNOME handle IBusMessage
-
-The IBusEngine's focus-id feature is now always used in case the engine
-is IBusEngineSimple to make a workaround in GNOME and Wayland because
-GNOME-Shell does not implement the new D-Bus method
-"SendMessageReceived" yet and the Wayland input-method protocol cannot
-get the input surface for the Wayland xdg-system-bell protocol.
-
-The auxiliary text is shown with the candidate popup in GNOME as a
-workaround to notify the Unicode loading message until GNOME-Shell
-implements the D-Bus method "SendMessageReceived".
-
-Update the POT file.
-
-BUG=https://github.com/ibus/ibus/issues/2722
----
- po/POTFILES.in              |   2 +
- src/ibusengine.c            |  18 ++-
- src/ibusenginesimple.c      | 134 ++++++++++++++++++++++-
- ui/gtk3/candidatepanel.vala |  55 +++++++---
- ui/gtk3/emojier.vala        |  44 ++++++--
- ui/gtk3/extension.vala      |   3 +-
- ui/gtk3/panelbinding.vala   |   5 +-
- 7 files changed, 338 insertions(+), 134 deletions(-)
-
-diff --git a/po/POTFILES.in b/po/POTFILES.in
-index c56408f7..d9178c85 100644
---- a/po/POTFILES.in
-+++ b/po/POTFILES.in
-@@ -40,9 +40,11 @@ setup/keyboardshortcut.py
- setup/main.py
- setup/setup.ui
- src/ibusbus.c
-+src/ibuscomposetable.c
- src/ibusconfig.c
- src/ibusemojigen.h
- src/ibusengine.c
-+src/ibusenginesimple.c
- src/ibusfactory.c
- src/ibushotkey.c
- src/ibusinputcontext.c
-diff --git a/src/ibusengine.c b/src/ibusengine.c
-index 4e13b23d..1f12d253 100644
---- a/src/ibusengine.c
-+++ b/src/ibusengine.c
-@@ -34,6 +34,8 @@
- #define IBUS_ENGINE_GET_PRIVATE(o)  \
-    ((IBusEnginePrivate *)ibus_engine_get_instance_private (o))
- 
-+extern GType   ibus_engine_simple_get_type       (void);
-+
- enum {
-     PROCESS_KEY_EVENT,
-     FOCUS_IN,
-@@ -1544,7 +1546,21 @@ _ibus_engine_has_focus_id (IBusEngine      *engine,
-                            GDBusConnection *connection,
-                            GError         **error)
- {
--    GVariant *retval = g_variant_new_boolean (engine->priv->has_focus_id);
-+    GVariant *retval;
-+
-+#ifndef IBUS_TYPE_ENGINE_SIMPLE
-+#define IBUS_TYPE_ENGINE_SIMPLE (ibus_engine_simple_get_type ())
-+#define __IBUS_SET_LOCAL_ENGINE_SIMPLE
-+#endif
-+    /* Should not use IBUS_IS_ENGINE_SIMPLE() not to effect the inherited
-+     * class.*/
-+    if (G_OBJECT_TYPE (engine) == IBUS_TYPE_ENGINE_SIMPLE)
-+        engine->priv->has_focus_id = TRUE;
-+#ifdef __IBUS_SET_LOCAL_ENGINE_SIMPLE
-+#undef __IBUS_SET_LOCAL_ENGINE_SIMPLE
-+#undef IBUS_TYPE_ENGINE_SIMPLE
-+#endif
-+    retval = g_variant_new_boolean (engine->priv->has_focus_id);
-     g_assert (retval);
-     return retval;
- }
-diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
-index 4e0ba40f..15c45c2e 100644
---- a/src/ibusenginesimple.c
-+++ b/src/ibusenginesimple.c
-@@ -75,6 +75,7 @@ struct _IBusEngineSimplePrivate {
-     GString            *tentative_match;
-     int                 tentative_match_len;
-     char               *tentative_emoji;
-+    char               *client;
- 
-     guint               hex_mode_enabled : 1;
-     guint               in_hex_sequence : 1;
-@@ -85,6 +86,8 @@ struct _IBusEngineSimplePrivate {
-     IBusLookupTable    *lookup_table;
-     gboolean            lookup_table_visible;
-     IBusText           *updated_preedit;
-+    gboolean            do_inform_user_error;
-+    guint               inform_user_error_timeout_id;
- };
- 
- guint COMPOSE_BUFFER_SIZE = 20;
-@@ -96,7 +99,13 @@ static IBusComposeTableEx *en_compose_table;
- /* functions prototype */
- static void     ibus_engine_simple_destroy      (IBusEngineSimple   *simple);
- static void     ibus_engine_simple_focus_in     (IBusEngine         *engine);
-+static void     ibus_engine_simple_focus_in_id  (IBusEngine         *engine,
-+                                                 const gchar
-+                                                                   *object_path,
-+                                                 const gchar        *client);
- static void     ibus_engine_simple_focus_out    (IBusEngine         *engine);
-+static void     ibus_engine_simple_focus_out_id (IBusEngine         *engine,
-+                                                 const gchar        *client);
- static void     ibus_engine_simple_reset        (IBusEngine         *engine);
- static gboolean ibus_engine_simple_process_key_event
-                                                 (IBusEngine         *engine,
-@@ -135,7 +144,11 @@ ibus_engine_simple_class_init (IBusEngineSimpleClass *class)
-         (IBusObjectDestroyFunc) ibus_engine_simple_destroy;
- 
-     engine_class->focus_in  = ibus_engine_simple_focus_in;
-+    engine_class->focus_in_id
-+                            = ibus_engine_simple_focus_in_id;
-     engine_class->focus_out = ibus_engine_simple_focus_out;
-+    engine_class->focus_out_id
-+                            = ibus_engine_simple_focus_out_id;
-     engine_class->reset     = ibus_engine_simple_reset;
-     engine_class->process_key_event
-                             = ibus_engine_simple_process_key_event;
-@@ -158,6 +171,22 @@ ibus_engine_simple_class_init (IBusEngineSimpleClass *class)
-     en_compose_table = ibus_compose_table_deserialize (contents, length);
- }
- 
-+
-+static gboolean
-+inform_user_error_timeout_cb (gpointer user_data)
-+{
-+    IBusEngineSimple *simple = (IBusEngineSimple *)user_data;
-+    IBusEngineSimplePrivate *priv;
-+
-+    g_return_val_if_fail (IBUS_IS_ENGINE_SIMPLE (simple), G_SOURCE_REMOVE);
-+    priv = simple->priv;
-+    priv->do_inform_user_error = FALSE;
-+    priv->inform_user_error_timeout_id = 0;
-+    ibus_engine_show_preedit_text ((IBusEngine *)simple);
-+    return G_SOURCE_REMOVE;
-+}
-+
-+
- static void
- ibus_engine_simple_init (IBusEngineSimple *simple)
- {
-@@ -205,13 +234,18 @@ ibus_engine_simple_destroy (IBusEngineSimple *simple)
-         IBUS_OBJECT (simple));
- }
- 
-+
- static void
- ibus_engine_simple_send_message_with_code (IBusEngineSimple *simple,
-                                            IBusEngineMsgCode code)
- {
-+    IBusEngineSimplePrivate *priv;
-     IBusMessage *message;
- 
-     g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple));
-+
-+    priv = simple->priv;
-+    priv->do_inform_user_error = TRUE;
-     message = ibus_message_new (
-             IBUS_MESSAGE_DOMAIN_ENGINE,
-             code,
-@@ -226,19 +260,57 @@ ibus_engine_simple_send_message_with_code (IBusEngineSimple *simple,
-     ibus_engine_send_message (IBUS_ENGINE (simple), message);
- }
- 
-+
- static void
- ibus_engine_simple_focus_in (IBusEngine *engine)
- {
-+    ibus_engine_simple_focus_in_id (engine, NULL, NULL);
-+}
-+
-+
-+static void
-+ibus_engine_simple_focus_in_id (IBusEngine  *engine,
-+                                const gchar *object_path,
-+                                const gchar *client)
-+{
-+    /* Do not enable IBusEngine:has-focus-id in the IBusEngineSimple's
-+     * constructor because the property should be enabled by the inherited
-+     * engine. E.g. IBusEngineHangul disables it.
-+     * The property is handled by ibusengine.c:_ibus_engine_has_focus_id();
-+     */
-+    if (IBUS_IS_ENGINE_SIMPLE (engine)) {
-+        IBusEngineSimple *simple = IBUS_ENGINE_SIMPLE (engine);
-+        g_free (simple->priv->client);
-+        simple->priv->client = g_strdup (client);
-+    } else {
-+        g_warning ("IBUS_IS_ENGINE_SIMPLE(engine) in %s", G_STRFUNC);
-+    }
-     IBUS_ENGINE_CLASS (ibus_engine_simple_parent_class)->focus_in (engine);
- }
- 
-+
- static void
- ibus_engine_simple_focus_out (IBusEngine *engine)
- {
--    ibus_engine_simple_reset (engine);
-+    ibus_engine_simple_focus_out_id (engine, NULL);
-+}
-+
-+
-+static void
-+ibus_engine_simple_focus_out_id (IBusEngine  *engine,
-+                                 const gchar *object_path)
-+{
-+    if (IBUS_IS_ENGINE_SIMPLE (engine)) {
-+        IBusEngineSimple *simple = IBUS_ENGINE_SIMPLE (engine);
-+        g_clear_pointer (&simple->priv->client, g_free);
-+        ibus_engine_simple_reset (engine);
-+    } else {
-+        g_warning ("IBUS_IS_ENGINE_SIMPLE(engine) in %s", G_STRFUNC);
-+    }
-     IBUS_ENGINE_CLASS (ibus_engine_simple_parent_class)->focus_out (engine);
- }
- 
-+
- static void
- ibus_engine_simple_reset (IBusEngine *engine)
- {
-@@ -259,11 +331,17 @@ ibus_engine_simple_reset (IBusEngine *engine)
-         priv->tentative_match_len = 0;
-     }
-     ibus_engine_hide_preedit_text ((IBusEngine *)simple);
-+    priv->do_inform_user_error = FALSE;
-+    if (priv->inform_user_error_timeout_id) {
-+        g_source_remove (priv->inform_user_error_timeout_id);
-+        priv->inform_user_error_timeout_id = 0;
-+    }
-     g_object_unref (priv->updated_preedit);
-     priv->updated_preedit =
-             (IBusText *)g_object_ref_sink (updated_preedit_empty);
- }
- 
-+
- static void
- ibus_engine_simple_commit_char (IBusEngineSimple *simple,
-                                 gunichar          ch)
-@@ -326,6 +404,7 @@ ibus_engine_simple_commit_str (IBusEngineSimple *simple,
-     g_free (backup_str);
- }
- 
-+
- static void
- ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
- {
-@@ -426,6 +505,11 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
-             g_object_unref (priv->updated_preedit);
-             priv->updated_preedit =
-                     (IBusText *)g_object_ref_sink (updated_preedit_empty);
-+            priv->do_inform_user_error = FALSE;
-+            if (priv->inform_user_error_timeout_id) {
-+                g_source_remove (priv->inform_user_error_timeout_id);
-+                priv->inform_user_error_timeout_id = 0;
-+            }
-         }
-     } else if (s->len >= G_MAXINT) {
-         g_warning ("%s is too long compose length: %lu", s->str, s->len);
-@@ -433,9 +517,30 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
-         guint len = (guint)g_utf8_strlen (s->str, -1);
-         IBusText *text = ibus_text_new_from_string (s->str);
-         ibus_text_append_attribute (text,
--                IBUS_ATTR_TYPE_UNDERLINE, IBUS_ATTR_UNDERLINE_SINGLE, 0, len);
-+                                    IBUS_ATTR_TYPE_UNDERLINE,
-+                                    IBUS_ATTR_UNDERLINE_SINGLE,
-+                                    0,
-+                                    len);
-         g_object_ref_sink (text);
--        ibus_engine_update_preedit_text ((IBusEngine *)simple, text, len, TRUE);
-+        /* gnome-shell does not handle "SendMessageReceived" D-Bus method yet.
-+         * Seems other Wayland desktops do not implement xdg-system-bell
-+         * Wayland protocol yet.
-+         */
-+        if (priv->do_inform_user_error && priv->client &&
-+            (!g_ascii_strncasecmp (priv->client, "gnome-shell", 11) ||
-+             !g_ascii_strncasecmp (priv->client, "wayland", 7))
-+           ) {
-+            ibus_engine_hide_preedit_text ((IBusEngine *)simple);
-+            if (priv->inform_user_error_timeout_id)
-+                g_source_remove (priv->inform_user_error_timeout_id);
-+            priv->inform_user_error_timeout_id =
-+                    g_timeout_add (500,
-+                                   inform_user_error_timeout_cb,
-+                                   simple);
-+        } else {
-+            ibus_engine_update_preedit_text ((IBusEngine *)simple,
-+                                             text, len, TRUE);
-+        }
-         g_object_unref (priv->updated_preedit);
-         priv->updated_preedit = text;
-     }
-@@ -460,6 +565,7 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
-  */
- #define HEX_MOD_MASK (IBUS_CONTROL_MASK | IBUS_SHIFT_MASK)
- 
-+
- static gboolean
- check_hex (IBusEngineSimple *simple,
-            int               n_compose)
-@@ -523,6 +629,7 @@ check_hex (IBusEngineSimple *simple,
-     return TRUE;
- }
- 
-+
- static IBusEngineDict *
- load_emoji_dict (void)
- {
-@@ -546,6 +653,7 @@ load_emoji_dict (void)
-     return emoji_dict;
- }
- 
-+
- static gboolean
- check_emoji_table (IBusEngineSimple       *simple,
-                    int                     n_compose,
-@@ -727,6 +835,7 @@ no_sequence_matches (IBusEngineSimple *simple,
-     return FALSE;
- }
- 
-+
- static gboolean
- is_hex_keyval (guint keyval)
- {
-@@ -735,6 +844,7 @@ is_hex_keyval (guint keyval)
-   return g_unichar_isxdigit (ch);
- }
- 
-+
- static gboolean
- is_graph_keyval (guint keyval)
- {
-@@ -743,6 +853,7 @@ is_graph_keyval (guint keyval)
-   return g_unichar_isgraph (ch);
- }
- 
-+
- static void
- ibus_engine_simple_update_lookup_and_aux_table (IBusEngineSimple *simple)
- {
-@@ -768,6 +879,7 @@ ibus_engine_simple_update_lookup_and_aux_table (IBusEngineSimple *simple)
-                                      priv->lookup_table_visible);
- }
- 
-+
- static gboolean
- ibus_engine_simple_if_in_range_of_lookup_table (IBusEngineSimple *simple,
-                                                 guint             keyval)
-@@ -794,6 +906,7 @@ ibus_engine_simple_if_in_range_of_lookup_table (IBusEngineSimple *simple,
-     return TRUE;
- }
- 
-+
- static void
- ibus_engine_simple_set_number_on_lookup_table (IBusEngineSimple *simple,
-                                                guint             keyval,
-@@ -886,8 +999,9 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
-                 if (is_32bit) {
-                     ibus_engine_simple_commit_str (simple, output->str);
-                 } else {
--                    ibus_engine_simple_commit_char (simple,
--                                                    g_utf8_get_char (output->str));
-+                    ibus_engine_simple_commit_char (
-+                            simple,
-+                            g_utf8_get_char (output->str));
-                 }
-             }
-             ibus_engine_simple_update_preedit_text (simple);
-@@ -951,8 +1065,10 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-     guint printable_keyval;
-     int i;
- 
--    while (n_compose <= COMPOSE_BUFFER_SIZE && priv->compose_buffer[n_compose] != 0)
-+    while (n_compose <= COMPOSE_BUFFER_SIZE &&
-+           priv->compose_buffer[n_compose] != 0) {
-         n_compose++;
-+    }
-     if (n_compose > COMPOSE_BUFFER_SIZE) {
-         g_warning ("copmose table buffer is full.");
-         n_compose = COMPOSE_BUFFER_SIZE;
-@@ -1395,6 +1511,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-     return no_sequence_matches (simple, n_compose, keyval, keycode, modifiers);
- }
- 
-+
- static void
- ibus_engine_simple_page_down (IBusEngine *engine)
- {
-@@ -1406,6 +1523,7 @@ ibus_engine_simple_page_down (IBusEngine *engine)
-     ibus_engine_simple_update_lookup_and_aux_table (simple);
- }
- 
-+
- static void
- ibus_engine_simple_page_up (IBusEngine *engine)
- {
-@@ -1417,6 +1535,7 @@ ibus_engine_simple_page_up (IBusEngine *engine)
-     ibus_engine_simple_update_lookup_and_aux_table (simple);
- }
- 
-+
- static void
- ibus_engine_simple_candidate_clicked (IBusEngine *engine,
-                                       guint       index,
-@@ -1440,6 +1559,7 @@ ibus_engine_simple_candidate_clicked (IBusEngine *engine,
-     ibus_engine_simple_set_number_on_lookup_table (simple, keyval, n_compose);
- }
- 
-+
- void
- ibus_engine_simple_add_table (IBusEngineSimple *simple,
-                               const guint16    *data,
-@@ -1454,6 +1574,7 @@ ibus_engine_simple_add_table (IBusEngineSimple *simple,
-                                                        n_seqs);
- }
- 
-+
- gboolean
- ibus_engine_simple_add_table_by_locale (IBusEngineSimple *simple,
-                                         const gchar      *locale)
-@@ -1572,6 +1693,7 @@ ibus_engine_simple_add_table_by_locale (IBusEngineSimple *simple,
-     return retval;
- }
- 
-+
- gboolean
- ibus_engine_simple_add_compose_file (IBusEngineSimple *simple,
-                                      const gchar      *compose_file)
-diff --git a/ui/gtk3/candidatepanel.vala b/ui/gtk3/candidatepanel.vala
-index aa1a34ce..a4137f05 100644
---- a/ui/gtk3/candidatepanel.vala
-+++ b/ui/gtk3/candidatepanel.vala
-@@ -491,6 +491,11 @@ public class CandidatePanel : Gtk.Box{
-         m_toplevel.hide();
-     }
- 
-+    /**
-+     * move:
-+     * @x: left position of the #CandidatePanel
-+     * @y: top position of the #CandidatePanel
-+     */
-     private void move(int x, int y) {
-         m_toplevel.move(x, y);
-     }
-@@ -502,21 +507,15 @@ public class CandidatePanel : Gtk.Box{
-             adjust_window_position_vertical(window);
-     }
- 
--    private Gdk.Rectangle get_monitor_geometry(Gtk.Widget window) {
--        Gdk.Rectangle monitor_area = { 0, };
--
--        // Use get_monitor_geometry() instead of get_monitor_area().
--        // get_monitor_area() excludes docks, but the lookup window should be
--        // shown over them.
--        Gdk.Monitor monitor = window.get_display().get_monitor_at_point(
--                m_cursor_location.x,
--                m_cursor_location.y);
--        monitor_area = monitor.get_geometry();
--        return monitor_area;
--    }
--
-+    /**
-+     * adjust_window_position_horizontal:
-+     * @window: A Gtk.Widget of the toplevel window.
-+     *
-+     * Horizontal writing mode but not the horizontal lookup table
-+     * when the allocation is emmitted.
-+     */
-     private void adjust_window_position_horizontal(Gtk.Widget window) {
--        Gdk.Point cursor_right_bottom = {
-+        Gdk.Point cursor_left_bottom = {
-                 m_cursor_location.x,
-                 m_cursor_location.y + m_cursor_location.height
-         };
-@@ -524,8 +523,8 @@ public class CandidatePanel : Gtk.Box{
-         Gtk.Allocation allocation;
-         m_toplevel.get_allocation(out allocation);
-         Gdk.Point window_right_bottom = {
--            cursor_right_bottom.x + allocation.width,
--            cursor_right_bottom.y + allocation.height
-+            cursor_left_bottom.x + allocation.width,
-+            cursor_left_bottom.y + allocation.height
-         };
- 
-         Gdk.Rectangle monitor_area = get_monitor_geometry(window);
-@@ -536,20 +535,27 @@ public class CandidatePanel : Gtk.Box{
-         if (window_right_bottom.x > monitor_right)
-             x = monitor_right - allocation.width;
-         else
--            x = cursor_right_bottom.x;
-+            x = cursor_left_bottom.x;
-         if (x < 0)
-             x = 0;
- 
-         if (window_right_bottom.y > monitor_bottom)
-             y = m_cursor_location.y - allocation.height;
-         else
--            y = cursor_right_bottom.y;
-+            y = cursor_left_bottom.y;
-         if (y < 0)
-             y = 0;
- 
-         move(x, y);
-     }
- 
-+    /**
-+     * adjust_window_position_vertical:
-+     * @window: A Gtk.Widget of the toplevel window.
-+     *
-+     * Vertical writing mode but not the vertical lookup table
-+     * when the allocation is emmitted.
-+     */
-     private void adjust_window_position_vertical(Gtk.Widget window) {
-         /* Not sure in which top or left cursor appears
-          * in the vertical writing mode.
-@@ -611,6 +617,19 @@ public class CandidatePanel : Gtk.Box{
-         move(x, y);
-     }
- 
-+    private Gdk.Rectangle get_monitor_geometry(Gtk.Widget window) {
-+        Gdk.Rectangle monitor_area = { 0, };
-+
-+        // Use get_monitor_geometry() instead of get_monitor_area().
-+        // get_monitor_area() excludes docks, but the lookup window should be
-+        // shown over them.
-+        Gdk.Monitor monitor = window.get_display().get_monitor_at_point(
-+                m_cursor_location.x,
-+                m_cursor_location.y);
-+        monitor_area = monitor.get_geometry();
-+        return monitor_area;
-+    }
-+
- #if USE_GDK_WAYLAND
-     private void realize_window(bool initial) {
-         // The custom surface can be used when the Wayland input-method
-diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 2d8754a4..8c527eca 100644
---- a/ui/gtk3/emojier.vala
-+++ b/ui/gtk3/emojier.vala
-@@ -223,8 +223,6 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         BACKWARD,
-     }
- 
--    public bool is_wayland { get; set; }
--
-     public const uint BUTTON_CLOSE_BUTTON = 1000;
- 
-     private const uint EMOJI_GRID_PAGE = 10;
-@@ -268,6 +266,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     private static bool m_loaded_unicode = false;
-     private static string m_warning_message = "";
- 
-+    private bool m_is_wayland;
-+    private bool m_is_gnome = false;
-     private ThemedRGBA m_rgba;
-     private Gtk.Box m_vbox;
-     /* If emojier is emoji category list or Unicode category list,
-@@ -328,7 +328,8 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-         GLib.Object(
-             type : Gtk.WindowType.POPUP
-         );
--        this.is_wayland = is_wayland;
-+        m_is_wayland = is_wayland;
-+        m_is_gnome = is_gnome();
- 
-         // GLib.ActionEntry accepts const variables only.
-         var action = new GLib.SimpleAction.stateful(
-@@ -382,6 +383,23 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     }
- 
- 
-+    private bool is_gnome() {
-+        unowned string? desktop =
-+            Environment.get_variable("XDG_CURRENT_DESKTOP");
-+        if (desktop == "GNOME")
-+            return true;
-+        /* If ibus-dameon is launched from systemd, XDG_CURRENT_DESKTOP
-+         * environment variable could be set after ibus-dameon would be
-+         * launched and XDG_CURRENT_DESKTOP could be "(null)".
-+         * But XDG_SESSION_DESKTOP can be set with systemd's PAM.
-+         */
-+        if (desktop == null || desktop == "(null)")
-+            desktop = Environment.get_variable("XDG_SESSION_DESKTOP");
-+        if (desktop == "gnome")
-+            return true;
-+        return false;
-+    }
-+
-     private static void reload_emoji_dict() {
-         init_emoji_dict();
-         make_emoji_dict("en");
-@@ -1807,7 +1825,7 @@ public class IBusEmojier : Gtk.ApplicationWindow {
- 
- 
-     private void start_rebuild_gui(bool initial_launching) {
--        if (!this.is_wayland)
-+        if (!m_is_wayland)
-             return;
-         if (!initial_launching && !base.get_visible())
-             return;
-@@ -2189,17 +2207,23 @@ public class IBusEmojier : Gtk.ApplicationWindow {
- 
- 
-     public IBus.Text get_title_text() {
-+        if (!m_loaded_unicode && m_is_gnome) {
-+            unichar c = 0x26A0;
-+            return new IBus.Text.from_string("%s%s %u%%".printf(
-+                    c.to_string(),
-+                    _("Loading a Unicode dictionary:"),
-+                    (uint)(m_unicode_percent * 100)));
-+        }
-         var language = _(IBus.get_language_name(m_current_lang_id));
-         uint ncandidates = this.get_number_of_candidates();
-         string main_title = _("Emoji Choice");
-         if (m_show_unicode)
-             main_title = _("Unicode Choice");
--        var text = new IBus.Text.from_string(
--                "%s (%s) (%u / %u)".printf(
--                        main_title,
--                        language,
--                        this.get_cursor_pos() + 1,
--                        ncandidates));
-+        var text = new IBus.Text.from_string("%s (%s) (%u / %u)".printf(
-+                main_title,
-+                language,
-+                this.get_cursor_pos() + 1,
-+                ncandidates));
-         int char_count = text.text.char_count();
-         int start_index = -1;
-         unowned string title = text.text;
-diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala
-index a6f2e8e6..5cd1a6ad 100644
---- a/ui/gtk3/extension.vala
-+++ b/ui/gtk3/extension.vala
-@@ -3,7 +3,7 @@
-  * ibus - The Input Bus
-  *
-  * Copyright(c) 2018 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright(c) 2018-2020 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright(c) 2018-2025 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
-@@ -99,6 +99,7 @@ class ExtensionGtk : Gtk.Application {
-     private void bus_disconnected(IBus.Bus bus) {
-         debug("connection is lost.");
-         Gtk.main_quit();
-+        this.quit();
-     }
- 
-     private void bus_connected(IBus.Bus bus) {
-diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala
-index f0827b3e..a2fd1441 100644
---- a/ui/gtk3/panelbinding.vala
-+++ b/ui/gtk3/panelbinding.vala
-@@ -632,6 +632,8 @@ class PanelBinding : IBus.PanelService {
-                     m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0);
-             } else {
-                 m_emojier.set_annotation(annotation);
-+                var text = m_emojier.get_title_text();
-+                show_candidate = (text.text.get_char() == 0x26A0);
-             }
-         }
-         convert_preedit_text();
-@@ -738,10 +740,11 @@ class PanelBinding : IBus.PanelService {
-     private void show_wayland_lookup_table(IBus.Text text) {
-         m_wayland_lookup_table_is_visible = true;
-         var table = m_emojier.get_one_dimension_lookup_table();
-+        bool do_show_auxiliary_text = (text.text.get_char() == 0x26A0);
-         uint ncandidates = table.get_number_of_candidates();
-         update_auxiliary_text_received(
-                 text,
--                ncandidates > 0 ? true : false);
-+                do_show_auxiliary_text ? true : ncandidates > 0 ? true : false);
-         update_lookup_table_received(
-                 table,
-                 ncandidates > 0 ? true : false);
--- 
-2.49.0
-
-From a1c1469a2ba70c013f06995fd7665f3e784cbaf0 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 5 Jun 2025 19:36:20 +0900
-Subject: [PATCH 1/4] src/ibusenginesimple: Improve BEPO compose sequence visuals
-
-The French (BEPO, AFNOR) keymap utilizes pseudo-deadkeys within
-U+FDD0 to U+FDD9 range, which lack readable characters but the
-symbol file, /usr/share/X11/xkb/symbols/fr, details the proposed
-expressions of the pseudo-deadkeys in the comment lines and IBus
-follows it.
-
-BUG=https://github.com/ibus/ibus/issues/2748
-BUG=https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8368
----
- src/ibuscomposetable.c        | 27 +++++++++++++++++-
- src/ibusenginesimple.c        | 52 ++++++++++++++++++++++++++++++++---
- src/ibusenginesimpleprivate.h | 12 ++++++++
- 3 files changed, 86 insertions(+), 5 deletions(-)
-
-diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
-index ffd40c75..4764426a 100644
---- a/src/ibuscomposetable.c
-+++ b/src/ibuscomposetable.c
-@@ -2058,7 +2058,7 @@ ibus_keysym_to_unicode (guint     keysym,
-          * we use U+00B7, MIDDLE DOT.
-          */
-         return 0x00B7;
--    default:;
-+    default:
-         if (need_space)
-             *need_space = FALSE;
-     }
-@@ -2066,3 +2066,28 @@ ibus_keysym_to_unicode (guint     keysym,
- #undef CASE
- #undef CASE_COMBINE
- }
-+
-+gunichar
-+ibus_keysym_to_unicode_with_layout (guint                      keysym,
-+                                    gboolean                   combining,
-+                                    gboolean                  *need_space,
-+                                    const gchar               *layout,
-+                                    G_GNUC_UNUSED const gchar *variant) {
-+#define CASE_KEYSYM(keysym_val, unicode)                                      \
-+        case keysym_val:                                                      \
-+            if (need_space)                                                   \
-+                *need_space = FALSE;                                          \
-+            return unicode
-+    /* Refer (BEPO, AFNOR) comments in /usr/share/X11/xkb/symbols/fr file. */
-+    if (!g_ascii_strncasecmp (layout, "fr", 2)) {
-+        switch (keysym) {
-+            CASE_KEYSYM(0x0100FDD4, 0x00DF); /* ß */
-+            CASE_KEYSYM(0x0100FDD5, 0x1D49); /* ᵉ */
-+            CASE_KEYSYM(0x0100FDD7, 0x221E); /* ∞ */
-+            CASE_KEYSYM(0x0100FDD8, 0x2015); /* ― */
-+            default:;
-+        }
-+    }
-+#undef CASE_KEYSYM
-+    return ibus_keysym_to_unicode (keysym, combining, need_space);
-+}
-diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
-index 15c45c2e..258bd849 100644
---- a/src/ibusenginesimple.c
-+++ b/src/ibusenginesimple.c
-@@ -472,15 +472,59 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
-                         g_string_append_unichar (s, ch);
-                     }
-                 } else {
-+                    guint unknown_ch = 0;
-                     ch = ibus_keyval_to_unicode (keysym);
-                     if (ch) {
--                        g_string_append_unichar(s, ch);
-+                        /* Should provide ibus_unicode_get_script() for
-+                         * other IBus engines?
-+                         */
-+                        if (g_unichar_get_script (ch) !=
-+                            G_UNICODE_SCRIPT_UNKNOWN) {
-+                            g_string_append_unichar(s, ch);
-+                        } else {
-+                            unknown_ch = ch;
-+                        }
-                     /* Can send Unicode char as keysym with <Uxxxx> format
-                      * in comopse sequences and should not warn this case.
-                      */
--                    } else if (g_unichar_validate (keysym)) {
--                        ch = keysym;
--                        g_string_append_unichar(s, ch);
-+                    } else if (g_unichar_validate (keysym & 0xffff)) {
-+                        ch = keysym & 0xffff;
-+                        if (g_unichar_get_script (ch) !=
-+                            G_UNICODE_SCRIPT_UNKNOWN) {
-+                            g_string_append_unichar(s, ch);
-+                        } else {
-+                            unknown_ch = ch;
-+                        }
-+                    }
-+                    if (unknown_ch) {
-+                        gchar *name = NULL;
-+                        gchar *layout = NULL;
-+                        gchar *layout_end;
-+                        g_object_get (simple, "engine-name", &name, NULL);
-+                        if (!g_ascii_strncasecmp (name, "xkb:", 4)) {
-+                            layout_end = name + 4;
-+                            while (*layout_end && *layout_end != ':')
-+                                layout_end++;
-+                            if (*layout_end == ':') {
-+                                layout = g_strndup (name + 4,
-+                                                    layout_end - name - 4);
-+                            }
-+                        } else {
-+                            g_warning ("Unexpected engine is used: %s", name);
-+                        }
-+                        g_free (name);
-+                        if (layout) {
-+                            ch = ibus_keysym_to_unicode_with_layout (keysym,
-+                                                                     FALSE,
-+                                                                     NULL,
-+                                                                     layout,
-+                                                                     NULL);
-+                            g_free (layout);
-+                        }
-+                        if (ch)
-+                            g_string_append_unichar(s, ch);
-+                        else
-+                            g_string_append_unichar(s, 0x00b7); /* · */
-                     }
-                 }
-                 if (!ch) {
-diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
-index ec764fa7..c01d2eb1 100644
---- a/src/ibusenginesimpleprivate.h
-+++ b/src/ibusenginesimpleprivate.h
-@@ -65,6 +65,18 @@ gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
- gunichar ibus_keysym_to_unicode     (guint                       keysym,
-                                      gboolean                    combining,
-                                      gboolean                   *need_space);
-+/**
-+ * ibus_keysym_to_unicode_with_layout:
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+gunichar ibus_keysym_to_unicode_with_layout
-+                                    (guint                       keysym,
-+                                     gboolean                    combining,
-+                                     gboolean                   *need_space,
-+                                     const gchar                *layout,
-+                                     G_GNUC_UNUSED const gchar  *variant);
- 
- G_END_DECLS
- 
--- 
-2.49.0
-
-From 065cb8316ffcb92d97803c8350f02408c478d7f2 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 5 Jun 2025 19:44:54 +0900
-Subject: [PATCH 2/4] src: Do not load en-US compose table by default
-
-GTK4 has load en-US compose table in case of no user compose file.
-GTK4 now does not load the en-US compose table by default in case
-that the user compose file exists or el-GR locale is used since
-4.18.5 and new keyword "include %L" can loads the en-US compose table.
-If the old compose cache version 4 is found, the compose file
-will be rewritten to add the line of "include %L".
-IBus also follows GTK 4.18.5.
-
-Fixes: https://github.com/ibus/ibus/commit/28b357d
-Fixes: https://github.com/ibus/ibus/commit/09ea069
-BUG=https://github.com/ibus/ibus/issues/2706
-BUG=https://github.com/ibus/ibus/issues/2727
----
- src/gencomposetable.c         |   9 +-
- src/ibuscomposetable.c        | 275 ++++++++++++++++++++++++++++------
- src/ibuscomposetable.h        |  24 ++-
- src/ibusengine.h              |   5 +-
- src/ibusenginesimple.c        | 139 ++++++++++++-----
- src/ibusenginesimpleprivate.h |  19 ++-
- src/tests/ibus-compose.c      |   6 +-
- 7 files changed, 414 insertions(+), 95 deletions(-)
-
-diff --git a/src/gencomposetable.c b/src/gencomposetable.c
-index 35d6ce83..161b89e0 100644
---- a/src/gencomposetable.c
-+++ b/src/gencomposetable.c
-@@ -1,8 +1,8 @@
- /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
- /* vim:set et sts=4: */
- /* ibus - The Input Bus
-- * Copyright (C) 2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
-- * Copyright (C) 2023 Red Hat, Inc.
-+ * Copyright (C) 2023-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2023-2025 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
-@@ -76,6 +76,7 @@ main (int argc, char *argv[])
-     char * const *sys_lang = NULL;
-     char *path = NULL;
-     IBusComposeTableEx *compose_table;
-+    guint16 saved_version = 0;
-     char *basename = NULL;
- 
-     path = g_strdup ("./Compose");
-@@ -97,13 +98,15 @@ main (int argc, char *argv[])
-         g_debug ("Create a cache of %s", path);
-     }
-     g_setenv ("IBUS_COMPOSE_CACHE_DIR", ".", TRUE);
--    compose_table = ibus_compose_table_load_cache (path);
-+    compose_table = ibus_compose_table_load_cache (path, &saved_version);
-     if (!compose_table &&
-         (compose_table = ibus_compose_table_new_with_file (path, NULL))
-            == NULL) {
-         g_warning ("Failed to generate the compose table.");
-         return 1;
-     }
-+    if (saved_version > 0)
-+        g_warning ("Old cache was updated.");
-     g_free (path);
-     basename = g_strdup_printf ("%08x.cache", compose_table->id);
-     g_debug ("Saving cache id %s", basename);
-diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
-index 4764426a..169ca8cb 100644
---- a/src/ibuscomposetable.c
-+++ b/src/ibuscomposetable.c
-@@ -24,6 +24,7 @@
- 
- #include <glib.h>
- #include <glib/gstdio.h>
-+#include <glib/gi18n-lib.h>
- #include <stdlib.h>
- #include <string.h>
- 
-@@ -38,7 +39,7 @@
- 
- 
- #define IBUS_COMPOSE_TABLE_MAGIC "IBusComposeTable"
--#define IBUS_COMPOSE_TABLE_VERSION (4)
-+#define IBUS_COMPOSE_TABLE_VERSION (5)
- #define IBUS_MAX_COMPOSE_ALGORITHM_LEN 9
- 
- typedef struct {
-@@ -283,10 +284,15 @@ expand_include_path (const char *include_path) {
-                 break;
-             }
-             case 'L': /* locale compose file */
--                g_warning ("while handling XCompose include target %s, found "
--                          "redundant %%L include; the include has been "
--                          "ignored", include_path);
--                goto fail;
-+                out = g_strdup ("%L");
-+                head = i + 2;
-+                while (*head || *head == ' ' || *head == '\t') head++;
-+                if (*head != '\0') {
-+                    g_warning ("\"%s\" after \"%%L\" is not supported in "
-+                               "XCompose include target.", head);
-+                    goto fail;
-+                }
-+                break;
-             case 'S': /* system compose dir */
-                 o = out;
-                 former = g_strndup (head, i - head);
-@@ -413,7 +419,8 @@ get_en_compose_file (void)
- 
- static GList *
- ibus_compose_list_parse_file (const char *compose_file,
--                              int        *max_compose_len)
-+                              int        *max_compose_len,
-+                              gboolean   *can_load_en_us)
- {
-     char *contents = NULL;
-     char **lines = NULL;
-@@ -423,6 +430,7 @@ ibus_compose_list_parse_file (const char *compose_file,
-     int i;
- 
-     g_assert (max_compose_len);
-+    g_assert (can_load_en_us);
- 
-     if (!g_file_get_contents (compose_file, &contents, &length, &error)) {
-         g_error ("%s", error->message);
-@@ -438,6 +446,11 @@ ibus_compose_list_parse_file (const char *compose_file,
-         parse_compose_line (&compose_list, lines[i], &compose_len, &include);
-         if (*max_compose_len < compose_len)
-             *max_compose_len = compose_len;
-+        if (!g_strcmp0 (include, "%L")) {
-+            *can_load_en_us = TRUE;
-+            g_clear_pointer (&include, g_free);
-+            continue;
-+        }
-         if (include && *include) {
-             GStatBuf buf_include = { 0, };
-             GStatBuf buf_parent = { 0, };
-@@ -486,7 +499,8 @@ ibus_compose_list_parse_file (const char *compose_file,
-         if (include && *include) {
-             GList *rest = ibus_compose_list_parse_file (
-                     include,
--                    max_compose_len);
-+                    max_compose_len,
-+                    can_load_en_us);
-             if (rest) {
-                 compose_list = g_list_concat (compose_list, rest);
-             }
-@@ -504,13 +518,16 @@ ibus_compose_list_check_duplicated_with_en (GList  *compose_list,
-                                             int     max_compose_len,
-                                             GSList *compose_tables)
- {
-+    guint *keysyms;
-+    GString *output;
-     GList *list;
--    static guint *keysyms;
-     GList *removed_list = NULL;
-     IBusComposeData *compose_data;
--    GString *output = g_string_new ("");
- 
-+    if (!compose_list)
-+        return NULL;
-     keysyms = g_new (guint, max_compose_len + 1);
-+    output = g_string_new ("");
- 
-     for (list = compose_list; list != NULL; list = list->next) {
-         int i;
-@@ -694,6 +711,8 @@ ibus_compose_list_check_duplicated_with_own (GList  *compose_list,
-     int i;
-     GList *removed_list = NULL;
- 
-+    if (!compose_list)
-+        return NULL;
-     for (list = compose_list; list != NULL; list = list->next) {
-         gboolean is_different_value = FALSE;
-         if (!list->next)
-@@ -921,6 +940,7 @@ ibus_compose_table_serialize (IBusComposeTableEx *compose_table,
-     guint16 max_seq_len;
-     guint16 index_stride;
-     guint16 n_seqs;
-+    guint8 compose_type = 0;
-     gsize n_seqs_32bit = 0;
-     gsize second_size = 0;
-     GVariant *variant_data = NULL;
-@@ -934,8 +954,9 @@ ibus_compose_table_serialize (IBusComposeTableEx *compose_table,
-     max_seq_len = compose_table->max_seq_len;
-     index_stride = max_seq_len + 2;
-     n_seqs = compose_table->n_seqs;
-+    compose_type = compose_table->can_load_en_us ? 1 : 0;
- 
--    g_return_val_if_fail (max_seq_len, NULL);
-+    g_return_val_if_fail (max_seq_len || compose_type, NULL);
- 
-     if (n_seqs) {
-         g_return_val_if_fail (compose_table->data, NULL);
-@@ -965,7 +986,7 @@ ibus_compose_table_serialize (IBusComposeTableEx *compose_table,
-         n_seqs_32bit = compose_table->priv->first_n_seqs;
-         second_size = compose_table->priv->second_size;
-     }
--    if (!n_seqs && !n_seqs_32bit) {
-+    if (!n_seqs && !n_seqs_32bit && !compose_type) {
-         g_warning ("ComposeTable has not key sequences.");
-         goto out_serialize;
-     } else if (n_seqs_32bit && !second_size) {
-@@ -1028,7 +1049,7 @@ ibus_compose_table_serialize (IBusComposeTableEx *compose_table,
-                 sizeof (guint32));
-         g_assert (variant_data_32bit_first && variant_data_32bit_second);
-     }
--    variant_table = g_variant_new ("(sqqqqqvvv)",
-+    variant_table = g_variant_new ("(sqqqqqvvvy)",
-                                    header,
-                                    version,
-                                    max_seq_len,
-@@ -1037,7 +1058,8 @@ ibus_compose_table_serialize (IBusComposeTableEx *compose_table,
-                                    second_size,
-                                    variant_data,
-                                    variant_data_32bit_first,
--                                   variant_data_32bit_second);
-+                                   variant_data_32bit_second,
-+                                   compose_type);
-     return g_variant_ref_sink (variant_table);
- 
- out_serialize:
-@@ -1061,7 +1083,8 @@ ibus_compose_table_find (gconstpointer data1,
- 
- IBusComposeTableEx *
- ibus_compose_table_deserialize (const char *contents,
--                                gsize       length)
-+                                gsize       length,
-+                                guint16    *saved_version)
- {
-     IBusComposeTableEx *retval = NULL;
-     GVariantType *type;
-@@ -1070,9 +1093,9 @@ ibus_compose_table_deserialize (const char *contents,
-     GVariant *variant_data_32bit_second = NULL;
-     GVariant *variant_table = NULL;
-     const char *header = NULL;
--    guint16 version = 0;
-     guint16 max_seq_len = 0;
-     guint16 n_seqs = 0;
-+    guint8 compose_type = 0;
-     guint16 n_seqs_32bit = 0;
-     guint16 second_size = 0;
-     guint16 index_stride;
-@@ -1083,7 +1106,9 @@ ibus_compose_table_deserialize (const char *contents,
- 
-     g_return_val_if_fail (contents != NULL, NULL);
-     g_return_val_if_fail (length > 0, NULL);
-+    g_assert (saved_version);
- 
-+    *saved_version = 0;
-     /* Check the cache version at first before load whole the file content. */
-     type = g_variant_type_new ("(sq)");
-     variant_table = g_variant_new_from_data (type,
-@@ -1100,25 +1125,24 @@ ibus_compose_table_deserialize (const char *contents,
-     }
- 
-     g_variant_ref_sink (variant_table);
--    g_variant_get (variant_table, "(&sq)", &header, &version);
-+    g_variant_get (variant_table, "(&sq)", &header, saved_version);
- 
-     if (g_strcmp0 (header, IBUS_COMPOSE_TABLE_MAGIC) != 0) {
-         g_warning ("cache is not IBusComposeTable.");
-         goto out_load_cache;
-     }
- 
--    if (version != IBUS_COMPOSE_TABLE_VERSION) {
-+    if (*saved_version != IBUS_COMPOSE_TABLE_VERSION) {
-         g_warning ("cache version is different: %u != %u",
--                   version, IBUS_COMPOSE_TABLE_VERSION);
-+                   *saved_version, IBUS_COMPOSE_TABLE_VERSION);
-         goto out_load_cache;
-     }
- 
--    version = 0;
-     header = NULL;
-     g_variant_unref (variant_table);
-     variant_table = NULL;
- 
--    type = g_variant_type_new ("(sqqqqqvvv)");
-+    type = g_variant_type_new ("(sqqqqqvvvy)");
-     variant_table = g_variant_new_from_data (type,
-                                              contents,
-                                              length,
-@@ -1133,7 +1157,7 @@ ibus_compose_table_deserialize (const char *contents,
-     }
- 
-     g_variant_ref_sink (variant_table);
--    g_variant_get (variant_table, "(&sqqqqqvvv)",
-+    g_variant_get (variant_table, "(&sqqqqqvvvy)",
-                    NULL,
-                    NULL,
-                    &max_seq_len,
-@@ -1142,12 +1166,15 @@ ibus_compose_table_deserialize (const char *contents,
-                    &second_size,
-                    &variant_data,
-                    &variant_data_32bit_first,
--                   &variant_data_32bit_second);
-+                   &variant_data_32bit_second,
-+                   &compose_type);
- 
-     if (max_seq_len == 0 || (n_seqs == 0 && n_seqs_32bit == 0)) {
--        g_warning ("cache size is not correct %d %d %d",
--                   max_seq_len, n_seqs, n_seqs_32bit);
--        goto out_load_cache;
-+        if (!compose_type) {
-+            g_warning ("cache size is not correct %d %d %d",
-+                       max_seq_len, n_seqs, n_seqs_32bit);
-+            goto out_load_cache;
-+        }
-     }
- 
-     if (n_seqs && variant_data) {
-@@ -1168,6 +1195,7 @@ ibus_compose_table_deserialize (const char *contents,
-         retval->data = data;
-     retval->max_seq_len = max_seq_len;
-     retval->n_seqs = n_seqs;
-+    retval->can_load_en_us = compose_type ? TRUE : FALSE;
- 
-     if (n_seqs_32bit && !second_size) {
-         g_warning ("32bit key sequences are loaded but the values " \
-@@ -1192,7 +1220,7 @@ ibus_compose_table_deserialize (const char *contents,
-             goto out_load_cache;
-         }
-     }
--    if (!data && !data_32bit_first) {
-+    if (!data && !data_32bit_first && !compose_type) {
-         g_warning ("cache data is null.");
-         goto out_load_cache;
-     }
-@@ -1235,7 +1263,8 @@ out_load_cache:
- 
- 
- IBusComposeTableEx *
--ibus_compose_table_load_cache (const gchar *compose_file)
-+ibus_compose_table_load_cache (const gchar *compose_file,
-+                               guint16     *saved_version)
- {
-     IBusComposeTableEx *retval = NULL;
-     guint32 hash;
-@@ -1246,6 +1275,8 @@ ibus_compose_table_load_cache (const gchar *compose_file)
-     gsize length = 0;
-     GError *error = NULL;
- 
-+    g_assert (saved_version);
-+    *saved_version = 0;
-     do {
-         hash = g_str_hash (compose_file);
-         if ((path = ibus_compose_hash_get_cache_path (hash)) == NULL)
-@@ -1270,7 +1301,9 @@ ibus_compose_table_load_cache (const gchar *compose_file)
-             break;
-         }
- 
--        retval = ibus_compose_table_deserialize (contents, length);
-+        retval = ibus_compose_table_deserialize (contents,
-+                                                 length,
-+                                                 saved_version);
-         if (retval == NULL) {
-             g_warning ("Failed to load the cache file: %s", path);
-         } else {
-@@ -1369,7 +1402,7 @@ ibus_compose_table_new_with_list (GList   *compose_list,
-     IBusComposeData *compose_data = NULL;
-     IBusComposeTableEx *retval = NULL;
- 
--    g_return_val_if_fail (compose_list != NULL, NULL);
-+    g_return_val_if_fail (compose_list, NULL);
- 
-     s_size_total = g_list_length (compose_list);
-     s_size_16bit = s_size_total;
-@@ -1498,6 +1531,7 @@ ibus_compose_table_new_with_list (GList   *compose_list,
-     retval->n_seqs = s_size_16bit;
-     retval->id = hash;
-     retval->rawdata = rawdata;
-+    retval->can_load_en_us = FALSE;
-     if (s_size_total > s_size_16bit) {
-         retval->priv = g_new0 (IBusComposeTablePrivate, 1);
-         retval->priv->data_first = ibus_compose_seqs_32bit_first;
-@@ -1515,6 +1549,8 @@ ibus_compose_table_new_with_file (const gchar *compose_file,
-                                   GSList      *compose_tables)
- {
-     GList *compose_list = NULL;
-+    gboolean can_load_en_us = FALSE;
-+    gboolean can_load_en_us_by_any = FALSE;
-     IBusComposeTableEx *compose_table;
-     int max_compose_len = 0;
-     int n_index_stride = 0;
-@@ -1522,13 +1558,29 @@ ibus_compose_table_new_with_file (const gchar *compose_file,
-     g_assert (compose_file != NULL);
- 
-     compose_list = ibus_compose_list_parse_file (compose_file,
--                                                 &max_compose_len);
--    if (compose_list == NULL)
-+                                                 &max_compose_len,
-+                                                 &can_load_en_us);
-+    if (compose_list == NULL && !can_load_en_us)
-         return NULL;
-     n_index_stride = max_compose_len + 2;
--    compose_list = ibus_compose_list_check_duplicated_with_en (compose_list,
--                                                               max_compose_len,
--                                                               compose_tables);
-+    can_load_en_us_by_any = can_load_en_us;
-+    if (!can_load_en_us_by_any) {
-+        GSList *l = compose_tables;
-+        while (l) {
-+            IBusComposeTableEx *table = l->data;
-+            if (table->can_load_en_us) {
-+                can_load_en_us_by_any = TRUE;
-+                break;
-+            }
-+            l = l->next;
-+        }
-+    }
-+    if (can_load_en_us_by_any) {
-+        compose_list = ibus_compose_list_check_duplicated_with_en (
-+                compose_list,
-+                max_compose_len,
-+                compose_tables);
-+    }
-     compose_list = g_list_sort_with_data (
-             compose_list,
-             (GCompareDataFunc) ibus_compose_data_compare,
-@@ -1539,8 +1591,19 @@ ibus_compose_table_new_with_file (const gchar *compose_file,
-             max_compose_len);
- 
-     if (compose_list == NULL) {
--        g_warning ("compose file %s does not include any keys besides keys "
--                   "in en-us compose file", compose_file);
-+        g_message ("compose file %s does not include any keys besides keys "
-+                   "in en-us compose file.\n", compose_file);
-+        if (can_load_en_us) {
-+            if (!(compose_table = g_new0 (IBusComposeTableEx, 1))) {
-+                g_warning ("Failed to alloc IBusComposeTableEx for %s.",
-+                            compose_file);
-+            } else {
-+                compose_table->id = g_str_hash (compose_file);
-+                compose_table->can_load_en_us = can_load_en_us;
-+                return compose_table;
-+            }
-+        }
-+
-         return NULL;
-     }
- 
-@@ -1552,6 +1615,8 @@ ibus_compose_table_new_with_file (const gchar *compose_file,
-             max_compose_len,
-             n_index_stride,
-             g_str_hash (compose_file));
-+    if (compose_table)
-+        compose_table->can_load_en_us = can_load_en_us;
- 
-     g_list_free_full (compose_list,
-                       (GDestroyNotify) ibus_compose_list_element_free);
-@@ -1560,6 +1625,73 @@ ibus_compose_table_new_with_file (const gchar *compose_file,
- }
- 
- 
-+void
-+ibus_compose_table_free (IBusComposeTableEx *compose_table)
-+{
-+    g_return_if_fail (compose_table);
-+    g_clear_pointer (&compose_table->priv, g_free);
-+    compose_table->data = NULL;
-+    compose_table->max_seq_len = 0;
-+    compose_table->n_seqs = 0;
-+    compose_table->id = 0;
-+    g_clear_pointer (&compose_table->rawdata, g_free);
-+    compose_table->can_load_en_us = FALSE;
-+    g_free (compose_table);
-+
-+}
-+
-+
-+static gboolean
-+rewrite_compose_file (const char *compose_file)
-+{
-+    static const char *prefix =
-+            "# IBus has rewritten this file to add the line:\n"
-+            "\n"
-+            "include \"%L\"\n"
-+            "\n"
-+            "# This is necessary to add your own Compose sequences\n"
-+            "# in addition to the builtin sequences of IBus. If this\n"
-+            "# is not what you want, just remove that line.\n"
-+            "#\n"
-+            "# A backup of the previous file contents has been made.\n"
-+            "\n"
-+            "\n";
-+
-+    char *content = NULL;
-+    gsize content_len;
-+    GFile *file = NULL;
-+    GOutputStream *stream = NULL;
-+    gboolean ret = FALSE;
-+
-+    if (!g_file_get_contents (compose_file, &content, &content_len, NULL))
-+        goto out;
-+
-+    file = g_file_new_for_path (compose_file);
-+    stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, TRUE, 0, NULL, NULL));
-+
-+    if (stream == NULL)
-+        goto out;
-+
-+    if (!g_output_stream_write (stream, prefix, strlen (prefix), NULL, NULL))
-+        goto out;
-+
-+    if (!g_output_stream_write (stream, content, content_len, NULL, NULL))
-+        goto out;
-+
-+    if (!g_output_stream_close (stream, NULL, NULL))
-+        goto out;
-+
-+    ret = TRUE;
-+
-+out:
-+    g_clear_object (&stream);
-+    g_clear_object (&file);
-+    g_clear_pointer (&content, g_free);
-+
-+    return ret;
-+}
-+
-+
- /* if ibus_compose_seqs[N - 1] is an outputed compose character,
-  * ibus_compose_seqs[N * 2 - 1] is also an outputed compose character.
-  * and ibus_compose_seqs[0] to ibus_compose_seqs[0 + N - 3] are the
-@@ -1609,13 +1741,17 @@ ibus_compose_table_list_add_array (GSList        *compose_tables,
- 
- GSList *
- ibus_compose_table_list_add_file (GSList      *compose_tables,
--                                  const gchar *compose_file)
-+                                  const gchar *compose_file,
-+                                  GError     **error)
- {
-     guint32 hash;
-     IBusComposeTableEx *compose_table;
-+    guint16 saved_version = 0;
- 
--    g_return_val_if_fail (compose_file != NULL, compose_tables);
-+    g_return_val_if_fail (compose_file, compose_tables);
-+    g_assert (error);
- 
-+    *error = NULL;
-     hash = g_str_hash (compose_file);
-     if (g_slist_find_custom (compose_tables,
-                              GINT_TO_POINTER (hash),
-@@ -1623,15 +1759,61 @@ ibus_compose_table_list_add_file (GSList      *compose_tables,
-         return compose_tables;
-     }
- 
--    compose_table = ibus_compose_table_load_cache (compose_file);
-+    compose_table = ibus_compose_table_load_cache (compose_file,
-+                                                   &saved_version);
-     if (compose_table != NULL)
-         return g_slist_prepend (compose_tables, compose_table);
- 
--   if ((compose_table = ibus_compose_table_new_with_file (compose_file,
--                                                          compose_tables))
-+parse:
-+    if ((compose_table = ibus_compose_table_new_with_file (compose_file,
-+                                                           compose_tables))
-            == NULL) {
--       return compose_tables;
--   }
-+        return compose_tables;
-+    }
-+    if (saved_version > 0 && saved_version < 5 &&
-+        !compose_table->can_load_en_us && compose_table->n_seqs < 100) {
-+        if (rewrite_compose_file (compose_file)) {
-+            g_assert ((*error) == NULL);
-+            *error = g_error_new (
-+                    IBUS_COMPOSE_ERROR,
-+                    IBUS_ENGINE_MSG_CODE_UPDATE_COMPOSE_TABLE,
-+                    _("Since IBus 1.5.33, Compose files replace the "
-+                    "builtin\n"
-+                    "compose sequences. To keep them and add your own\n"
-+                    "sequences on top, the line:\n"
-+                    "\n"
-+                    "  include \"%%L\"\n"
-+                    "\n"
-+                    "has been added to the Compose file:\n%s.\n"),
-+                    compose_file);
-+            ibus_compose_table_free (compose_table);
-+            goto parse;
-+        } else {
-+            gchar *error_message1;
-+            gchar *error_message2;
-+            if (*error) {
-+                error_message1 = g_strdup_printf ("%s\n", (*error)->message);
-+                g_error_free (*error);
-+            } else {
-+                error_message1 = g_strdup ("");
-+            }
-+            error_message2 = g_strdup_printf (
-+                    _("Since IBus 1.5.33, Compose files replace the "
-+                    "builtin\n"
-+                    "compose sequences. To keep them and add your own\n"
-+                    "sequences on top, you need to add the line:\n"
-+                    "\n"
-+                    "  include \"%%L\"\n"
-+                    "\n"
-+                    "to the Compose file:\n%s."), compose_file);
-+            *error = g_error_new (
-+                    IBUS_COMPOSE_ERROR,
-+                    IBUS_ENGINE_MSG_CODE_UPDATE_COMPOSE_TABLE,
-+                    "%s%s", error_message1, error_message2);
-+            g_free (error_message1);
-+            g_free (error_message2);
-+        }
-+    }
- 
-     ibus_compose_table_save_cache (compose_table);
-     return g_slist_prepend (compose_tables, compose_table);
-@@ -2091,3 +2273,10 @@ ibus_keysym_to_unicode_with_layout (guint                      keysym,
- #undef CASE_KEYSYM
-     return ibus_keysym_to_unicode (keysym, combining, need_space);
- }
-+
-+
-+GQuark
-+ibus_compose_error_quark (void)
-+{
-+  return g_quark_from_static_string ("ibus-compose-error-quark");
-+}
-diff --git a/src/ibuscomposetable.h b/src/ibuscomposetable.h
-index 32e337f7..2068657d 100644
---- a/src/ibuscomposetable.h
-+++ b/src/ibuscomposetable.h
-@@ -2,7 +2,7 @@
- /* vim:set et sts=4: */
- /* ibus - The Input Bus
-  * Copyright (C) 2013-2014 Peng Huang <shawn.p.huang@gmail.com>
-- * Copyright (C) 2013-2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+ * Copyright (C) 2013-2025 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
-@@ -23,8 +23,19 @@
- #ifndef __IBUS_COMPOSETABLE_H_
- #define __IBUS_COMPOSETABLE_H_
- 
--#include <glib.h>
-+/**
-+ * SECTION: ibuscomposetable
-+ * @short_description: Compose table handling based on libX11
-+ * @title: IBusComposeTable
-+ * @stability: Unstable
-+ *
-+ * Generate #IBusComposeTableEx with the compose file or array.
-+ *
-+ * see_also: #IBusEngineSimple
-+ *
-+ */
- 
-+#include <glib.h>
- 
- G_BEGIN_DECLS
- 
-@@ -53,6 +64,7 @@ struct _IBusComposeTableEx
-     gint n_seqs;
-     guint32 id;
-     char *rawdata;
-+    gboolean can_load_en_us;
- };
- 
- 
-@@ -71,8 +83,11 @@ IBusComposeTableEx *
-                   ibus_compose_table_new_with_file (const gchar *compose_file,
-                                                     GSList
-                                                                *compose_tables);
-+void               ibus_compose_table_free         (IBusComposeTableEx
-+                                                                *compose_table);
- IBusComposeTableEx *
--                  ibus_compose_table_load_cache    (const gchar *compose_file);
-+                  ibus_compose_table_load_cache    (const gchar *compose_file,
-+                                                    guint16     *saved_version);
- void              ibus_compose_table_save_cache    (IBusComposeTableEx
-                                                                 *compose_table);
- GSList *          ibus_compose_table_list_add_array
-@@ -84,7 +99,8 @@ GSList *          ibus_compose_table_list_add_array
-                                                     gint         n_seqs);
- GSList *          ibus_compose_table_list_add_file (GSList
-                                                                 *compose_tables,
--                                                    const gchar *compose_file);
-+                                                    const gchar *compose_file,
-+                                                    GError     **error);
- GSList *          ibus_compose_table_list_add_table (GSList
-                                                                 *compose_tables,
-                                                      IBusComposeTableEx
-diff --git a/src/ibusengine.h b/src/ibusengine.h
-index cdc96331..6d0c1d1f 100644
---- a/src/ibusengine.h
-+++ b/src/ibusengine.h
-@@ -71,6 +71,8 @@ G_BEGIN_DECLS
-  * @IBUS_ENGINE_MSG_CODE_GENERAL: Generic message for Engine
-  * @IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE: User's typing failure
-  *         against the definition of the compose files.
-+ * @IBUS_ENGINE_MSG_CODE_UPDATE_COMPOSE_TABLE: Notification about new
-+ * behaviors or attentions when the compose table version is changed.
-  *
-  * Message codes in the `IBusMessageDomain` domain for Engine
-  * See also #IBusMessage, ibus_engine_send_message()
-@@ -81,7 +83,8 @@ G_BEGIN_DECLS
- typedef enum
- {
-   IBUS_ENGINE_MSG_CODE_GENERAL,
--  IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE
-+  IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+  IBUS_ENGINE_MSG_CODE_UPDATE_COMPOSE_TABLE
- } IBusEngineMsgCode;
- 
- typedef struct _IBusEngine IBusEngine;
-diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
-index 258bd849..7bee2403 100644
---- a/src/ibusenginesimple.c
-+++ b/src/ibusenginesimple.c
-@@ -139,6 +139,7 @@ ibus_engine_simple_class_init (IBusEngineSimpleClass *class)
-     GError *error = NULL;
-     const char *contents;
-     gsize length = 0;
-+    guint16 saved_version = 0;
- 
-     ibus_object_class->destroy =
-         (IBusObjectDestroyFunc) ibus_engine_simple_destroy;
-@@ -168,7 +169,14 @@ ibus_engine_simple_class_init (IBusEngineSimpleClass *class)
-         return;
-     }
-     contents = g_bytes_get_data (data, &length);
--    en_compose_table = ibus_compose_table_deserialize (contents, length);
-+    en_compose_table = ibus_compose_table_deserialize (contents,
-+                                                       length,
-+                                                       &saved_version);
-+    if (!en_compose_table && saved_version) {
-+        g_warning ("Failed to parse the builtin compose due to the different "
-+                   "version %u. Please rebuild IBus resource files.",
-+                   saved_version);
-+    }
- }
- 
- 
-@@ -237,7 +245,8 @@ ibus_engine_simple_destroy (IBusEngineSimple *simple)
- 
- static void
- ibus_engine_simple_send_message_with_code (IBusEngineSimple *simple,
--                                           IBusEngineMsgCode code)
-+                                           IBusEngineMsgCode code,
-+                                           GError           *error)
- {
-     IBusEngineSimplePrivate *priv;
-     IBusMessage *message;
-@@ -245,18 +254,33 @@ ibus_engine_simple_send_message_with_code (IBusEngineSimple *simple,
-     g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple));
- 
-     priv = simple->priv;
--    priv->do_inform_user_error = TRUE;
--    message = ibus_message_new (
--            IBUS_MESSAGE_DOMAIN_ENGINE,
--            code,
--            _("Detect unregistered character in your compose sequence"),
--            _("The character you just input is not recognized as a valid " \
--              "part of the currently active compose sequence and the " \
--              "character was cancelled. Try inputting the correct character " \
--              "to the compose sequence again, or press Escape key to " \
--              "terminate whole the compose sequence."),
--             "timeout", 5,
--             NULL);
-+    switch (code) {
-+    case IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE:
-+        priv->do_inform_user_error = TRUE;
-+        message = ibus_message_new (
-+                IBUS_MESSAGE_DOMAIN_ENGINE,
-+                code,
-+                _("Detect unregistered character in your compose sequence"),
-+                _("The character you just input is not recognized as a valid " \
-+                  "part of the currently active compose sequence and the " \
-+                  "character was cancelled. Try inputting the correct " \
-+                  "character to the compose sequence again, or press Escape " \
-+                  "key to terminate whole the compose sequence."),
-+                  "timeout", 5,
-+                  NULL);
-+        break;
-+    case IBUS_ENGINE_MSG_CODE_UPDATE_COMPOSE_TABLE:
-+        message = ibus_message_new (
-+                IBUS_MESSAGE_DOMAIN_ENGINE,
-+                code,
-+                _("Compose file update"),
-+                error ? error->message : "",
-+                "timeout", 120,
-+                NULL);
-+        break;
-+    default:
-+        g_assert_not_reached ();
-+    }
-     ibus_engine_send_message (IBUS_ENGINE (simple), message);
- }
- 
-@@ -853,7 +877,8 @@ no_sequence_matches (IBusEngineSimple *simple,
-             /* Invalid sequence */
-             ibus_engine_simple_send_message_with_code (
-                     simple,
--                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                    NULL);
-             ibus_engine_simple_update_preedit_text (simple);
-             return TRUE;
-         }
-@@ -997,6 +1022,7 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
-     GString *output = g_string_new ("");
-     gboolean success = FALSE;
-     gboolean is_32bit = FALSE;
-+    gboolean can_load_en_us = FALSE;
-     gunichar output_char = '\0';
- 
-     /* GtkIMContextSimple output the first compose char in case of
-@@ -1008,27 +1034,40 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
-     G_LOCK (global_tables);
-     tmp_list = global_tables;
-     while (tmp_list) {
-+        IBusComposeTableEx *compose_table = tmp_list->data;
-+        if (compose_table->can_load_en_us)
-+            can_load_en_us = TRUE;
-+        /* If global_tables includes en_compose_table only, i.e. no user
-+         * or locale compose tables, en_compose_table is used.
-+         * If not, en_compose_table is used in case one of the other compose
-+         * tables has can_load_en_us = %TRUE, i.e. the table file has
-+         * the line of 'include "%L"'.
-+         * en_compose_table is always appended to the last of global_tables.
-+         */
-+        if ((compose_table == en_compose_table) && global_tables->next != NULL
-+            && !can_load_en_us) {
-+            tmp_list = tmp_list->next;
-+            continue;
-+        }
-         is_32bit = FALSE;
--        if (ibus_compose_table_check (
--            (IBusComposeTableEx *)tmp_list->data,
--            priv->compose_buffer,
--            n_compose,
--            &compose_finish,
--            &compose_match,
--            output,
--            is_32bit)) {
-+        if (ibus_compose_table_check (compose_table,
-+                                      priv->compose_buffer,
-+                                      n_compose,
-+                                      &compose_finish,
-+                                      &compose_match,
-+                                      output,
-+                                      is_32bit)) {
-             success = TRUE;
-             break;
-         }
-         is_32bit = TRUE;
--        if (ibus_compose_table_check (
--            (IBusComposeTableEx *)tmp_list->data,
--            priv->compose_buffer,
--            n_compose,
--            &compose_finish,
--            &compose_match,
--            output,
--            is_32bit)) {
-+        if (ibus_compose_table_check (compose_table,
-+                                      priv->compose_buffer,
-+                                      n_compose,
-+                                      &compose_finish,
-+                                      &compose_match,
-+                                      output,
-+                                      is_32bit)) {
-             success = TRUE;
-             break;
-         }
-@@ -1134,7 +1173,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                 /* invalid hex sequence */
-                 ibus_engine_simple_send_message_with_code (
-                         simple,
--                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                        NULL);
-                 g_string_set_size (priv->tentative_match, 0);
-                 g_clear_pointer (&priv->tentative_emoji, g_free);
-                 priv->in_hex_sequence = FALSE;
-@@ -1159,7 +1199,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                 /* invalid hex sequence */
-                 ibus_engine_simple_send_message_with_code (
-                         simple,
--                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                        NULL);
-                 g_string_set_size (priv->tentative_match, 0);
-                 g_clear_pointer (&priv->tentative_emoji, g_free);
-                 priv->in_hex_sequence = FALSE;
-@@ -1210,7 +1251,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             /* invalid hex sequence */
-             ibus_engine_simple_send_message_with_code (
-                     simple,
--                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                    NULL);
-             g_string_set_size (priv->tentative_match, 0);
-             g_clear_pointer (&priv->tentative_emoji, g_free);
-             priv->in_hex_sequence = FALSE;
-@@ -1311,7 +1353,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             if (n_compose > 0) {
-                 ibus_engine_simple_send_message_with_code (
-                         simple,
--                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                        NULL);
-                 g_string_set_size (priv->tentative_match, 0);
-                 priv->in_hex_sequence = FALSE;
-                 priv->compose_buffer[0] = 0;
-@@ -1378,7 +1421,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             /* non-hex character in hex sequence */
-             ibus_engine_simple_send_message_with_code (
-                     simple,
--                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                    NULL);
-             return TRUE;
-         }
-     } else if (priv->in_emoji_sequence) {
-@@ -1439,7 +1483,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-                     /* invalid hex sequence */
-                     ibus_engine_simple_send_message_with_code (
-                             simple,
--                            IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                            IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                            NULL);
-                     g_string_set_size (priv->tentative_match, 0);
-                     priv->in_hex_sequence = FALSE;
-                     priv->compose_buffer[0] = 0;
-@@ -1447,7 +1492,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             } else if (!check_hex (simple, n_compose)) {
-                 ibus_engine_simple_send_message_with_code (
-                         simple,
--                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                        IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                        NULL);
-             }
-             ibus_engine_simple_update_preedit_text (simple);
- 
-@@ -1542,7 +1588,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
-             g_assert (n_compose < (COMPOSE_BUFFER_SIZE + 1));
-             ibus_engine_simple_send_message_with_code (
-                     simple,
--                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE);
-+                    IBUS_ENGINE_MSG_CODE_INVALID_COMPOSE_SEQUENCE,
-+                    NULL);
-             backup_char = priv->compose_buffer[n_compose];
-             priv->compose_buffer[n_compose] = 0;
-             if (ibus_engine_simple_check_all_compose_table (simple, n_compose))
-@@ -1742,9 +1789,19 @@ gboolean
- ibus_engine_simple_add_compose_file (IBusEngineSimple *simple,
-                                      const gchar      *compose_file)
- {
--    g_return_val_if_fail (IBUS_IS_ENGINE_SIMPLE (simple), TRUE);
-+    GError *error = NULL;
-+
-+    g_return_val_if_fail (IBUS_IS_ENGINE_SIMPLE (simple), FALSE);
- 
-     global_tables = ibus_compose_table_list_add_file (global_tables,
--                                                      compose_file);
-+                                                      compose_file,
-+                                                      &error);
-+    if (error) {
-+        g_warning ("\n%s\n", error->message);
-+        ibus_engine_simple_send_message_with_code (simple,
-+                                                   error->code,
-+                                                   error);
-+        g_error_free (error);
-+    }
-     return TRUE;
- }
-diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
-index c01d2eb1..c3f92920 100644
---- a/src/ibusenginesimpleprivate.h
-+++ b/src/ibusenginesimpleprivate.h
-@@ -26,6 +26,15 @@
- 
- G_BEGIN_DECLS
- 
-+/**
-+ * IBUS_COMPOSE_ERROR:
-+ *
-+ * The `GQuark` used for `IBusComposeTableEx` errors.
-+ *
-+ * Since: 1.5.33
-+ */
-+#define IBUS_COMPOSE_ERROR (ibus_compose_error_quark ())
-+
- /* Checks if a keysym is a dead key. Dead key keysym values are defined in
-  * ibuskeysyms.h and the first is GDK_KEY_dead_grave.
-  */
-@@ -42,6 +51,13 @@ struct _IBusComposeTablePrivate
- };
- 
- 
-+/**
-+ * ibus_compose_error_quark:
-+ *
-+ * Since: 1.5.33
-+ * Stability: Unstable
-+ */
-+GQuark   ibus_compose_error_quark   (void);
- guint    ibus_compose_key_flag      (guint                       key);
- gboolean ibus_check_algorithmically (const guint                *compose_buffer,
-                                      int                         n_compose,
-@@ -54,7 +70,8 @@ GVariant *
- IBusComposeTableEx *
-          ibus_compose_table_deserialize
-                                     (const char                 *contents,
--                                     gsize                       length);
-+                                     gsize                       length,
-+                                     guint16                    *saved_version);
- gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
-                                      guint                      *compose_buffer,
-                                      int                         n_compose,
-diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
-index 326d3b90..c07c02c4 100644
---- a/src/tests/ibus-compose.c
-+++ b/src/tests/ibus-compose.c
-@@ -241,9 +241,13 @@ create_engine_cb (IBusFactory *factory,
-     else
-         compose_path = get_compose_path ();
-     if (compose_path != NULL) {
-+        guint16 saved_version = 0;
-         ibus_engine_simple_add_compose_file (IBUS_ENGINE_SIMPLE (m_engine),
-                                              compose_path);
--        m_compose_table = ibus_compose_table_load_cache (compose_path);
-+        m_compose_table = ibus_compose_table_load_cache (compose_path,
-+                                                         &saved_version);
-+        if (m_compose_table)
-+            g_assert (saved_version);
-     }
-     g_free (compose_path);
-     return m_engine;
--- 
-2.49.0
-
-From 2b9c87bd4dc81fba0f21688f6f288734564f2590 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 5 Jun 2025 19:45:30 +0900
-Subject: [PATCH 3/4] engine: Add test-gnome.py to check gnome-desktop
-
-BUG=https://github.com/ibus/ibus/issues/2748
----
- configure.ac         |  15 +++++
- engine/Makefile.am   |  99 +++++++++++++++++---------------
- engine/test-gnome.py | 132 +++++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 200 insertions(+), 46 deletions(-)
- create mode 100755 engine/test-gnome.py
-
-diff --git a/configure.ac b/configure.ac
-index 16d56a4d..a5e4a44c 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -601,6 +601,20 @@ AC_ARG_WITH(gtk4-im-module-dir,
- )
- AC_SUBST(GTK4_IM_MODULEDIR)
- 
-+AC_MSG_CHECKING([for $PYTHON GI GnomeDesktop module])
-+if AC_RUN_LOG([$PYTHON -c "def _configure_test():
-+    from gi import require_version as gi_require_version
-+    gi_require_version('GnomeDesktop', '4.0')
-+    from gi.repository import GnomeDesktop
-+_configure_test()"]); then
-+    enable_pygnome_desktop="yes"
-+else
-+    enable_pygnome_desktop="no"
-+fi
-+AC_MSG_RESULT([$enable_pygnome_desktop])
-+AM_CONDITIONAL([ENABLE_PYGNOME_DESKTOP],
-+               [test x"$enable_pygnome_desktop" = x"yes"])
-+
- if test x"$enable_python" = x"yes"; then
-     # Check for dbus-python.
-     AC_ARG_ENABLE(dbus-python-check,
-@@ -941,6 +955,7 @@ Build options:
-   PYTHON                        $PYTHON
-   PYTHON2                       $PYTHON2
-   Python overrides dir          $pyoverridesdir
-+  Python GI GnomeDesktop module $enable_pygnome_desktop
-   Enable python2                $enable_python2
-   Gtk2 immodule dir             $GTK2_IM_MODULEDIR
-   Gtk3 immodule dir             $GTK3_IM_MODULEDIR
-diff --git a/engine/Makefile.am b/engine/Makefile.am
-index a2d6063a..3e4d045f 100644
---- a/engine/Makefile.am
-+++ b/engine/Makefile.am
-@@ -4,7 +4,7 @@
- #
- # Copyright (c) 2010-2016, Google Inc. All rights reserved.
- # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
--# Copyright (c) 2013-2023 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+# Copyright (c) 2013-2025 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
-@@ -24,76 +24,83 @@
- libibus = $(top_builddir)/src/libibus-@IBUS_API_VERSION@.la
- 
- AM_CPPFLAGS = \
--	-I$(top_srcdir)/src \
--	-I$(top_builddir)/src \
--	$(NULL)
-+    -I$(top_srcdir)/src \
-+    -I$(top_builddir)/src \
-+    $(NULL)
- 
- AM_CFLAGS = \
--	@GLIB2_CFLAGS@ \
--	@GIO2_CFLAGS@ \
--	@GTHREAD2_CFLAGS@ \
--	-DG_LOG_DOMAIN=\"IBUS\" \
--	-DPKGDATADIR=\"$(pkgdatadir)\" \
--	-DLIBEXECDIR=\"$(libexecdir)\" \
--	-DBINDIR=\"@bindir@\" \
-+    @GLIB2_CFLAGS@ \
-+    @GIO2_CFLAGS@ \
-+    @GTHREAD2_CFLAGS@ \
-+    -DG_LOG_DOMAIN=\"IBUS\" \
-+    -DPKGDATADIR=\"$(pkgdatadir)\" \
-+    -DLIBEXECDIR=\"$(libexecdir)\" \
-+    -DBINDIR=\"@bindir@\" \
-     -DIBUS_DISABLE_DEPRECATED \
--	-Wno-unused-variable \
--	-Wno-unused-but-set-variable \
--	-Wno-unused-function \
--	$(NULL)
-+    -Wno-unused-variable \
-+    -Wno-unused-but-set-variable \
-+    -Wno-unused-function \
-+    $(NULL)
- 
- AM_LDADD = \
--	@GOBJECT2_LIBS@ \
--	@GLIB2_LIBS@ \
--	@GIO2_LIBS@ \
--	@GTHREAD2_LIBS@ \
--	$(libibus) \
--	$(NULL)
-+    @GOBJECT2_LIBS@ \
-+    @GLIB2_LIBS@ \
-+    @GIO2_LIBS@ \
-+    @GTHREAD2_LIBS@ \
-+    $(libibus) \
-+    $(NULL)
- 
- AM_VALAFLAGS = \
--	--vapidir=$(top_builddir)/bindings/vala \
--	--pkg=ibus-1.0 \
-+    --vapidir=$(top_builddir)/bindings/vala \
-+    --pkg=ibus-1.0 \
-     --pkg-config="\"$(PKG_CONFIG) --with-path=$(PKG_CONFIG_PATH)\"" \
--	--target-glib="$(VALA_TARGET_GLIB_VERSION)" \
--	$(NULL)
-+    --target-glib="$(VALA_TARGET_GLIB_VERSION)" \
-+    $(NULL)
-+
-+TESTS =
-+
-+if ENABLE_PYGNOME_DESKTOP
-+TESTS += test-gnome.py
-+endif
- 
- libexec_PROGRAMS = \
--	ibus-engine-simple \
--	$(NULL)
-+    ibus-engine-simple \
-+    $(NULL)
- 
- ibus_engine_simple_SOURCES = \
--	main.vala \
--	$(NULL)
-+    main.vala \
-+    $(NULL)
- ibus_engine_simple_CFLAGS = \
--	$(AM_CFLAGS) \
--	$(NULL)
-+    $(AM_CFLAGS) \
-+    $(NULL)
- ibus_engine_simple_LDADD = \
--	$(AM_LDADD) \
--	$(NULL)
-+    $(AM_LDADD) \
-+    $(NULL)
- ibus_engine_simple_DEPENDENCIES = \
--	$(libibus) \
--	$(NULL)
-+    $(libibus) \
-+    $(NULL)
- 
- component_DATA = \
--	simple.xml \
--	$(NULL)
-+    simple.xml \
-+    $(NULL)
- 
- componentdir = $(pkgdatadir)/component
- 
- MAINTAINERCLEANFILES = \
--	simple.xml.in \
--	$(NULL)
-+    simple.xml.in \
-+    $(NULL)
- 
- CLEANFILES = \
--	simple.xml \
--	$(NULL)
-+    simple.xml \
-+    $(NULL)
- 
- EXTRA_DIST = \
--	denylist.txt \
--	gensimple.py \
--	iso639converter.py \
--	simple.xml.in \
--	$(NULL)
-+    denylist.txt \
-+    gensimple.py \
-+    iso639converter.py \
-+    simple.xml.in \
-+    test-gnome.py \
-+    $(NULL)
- 
- simple.xml: simple.xml.in
- 	$(AM_V_GEN) sed \
-diff --git a/engine/test-gnome.py b/engine/test-gnome.py
-new file mode 100755
-index 00000000..0a603fb0
---- /dev/null
-+++ b/engine/test-gnome.py
-@@ -0,0 +1,132 @@
-+#!/usr/bin/python
-+# vim:set fileencoding=utf-8 et sts=4 sw=4:
-+#
-+# ibus - Intelligent Input Bus for Linux / Unix OS
-+#
-+# Copyright © 2025 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, see <http://www.gnu.org/licenses/>.
-+
-+# Check gnome-shell/js/misc/keyboardManager.js
-+
-+from gi import require_version as gi_require_version
-+gi_require_version('GnomeDesktop', '4.0')
-+
-+from gi.repository import GnomeDesktop
-+
-+import os, re, sys
-+
-+
-+class TestGnomeDesktop:
-+    __srcdir = '.'
-+    __option_debug = False
-+    __simple_filename = 'simple.xml.in'
-+    __denylist_filename = 'denylist.txt'
-+    __xkb_info = None
-+    __simple_contents = None
-+    __denylist_lines = None
-+
-+    @classmethod
-+    def parse_args(cls):
-+        cls.__srcdir = os.path.dirname(sys.argv[0])
-+        if cls.__srcdir == '':
-+            cls.__srcdir = '.'
-+        if len(sys.argv) > 1:
-+            arg1 = sys.argv[1]
-+            if arg1 == '--debug':
-+                cls.__option_debug = True
-+
-+    def __init__(self):
-+        self.__simple_filename = '%s/%s' % (self.__srcdir,
-+                                            self.__simple_filename)
-+        self.__denylist_filename = '%s/%s' % (self.__srcdir,
-+                                              self.__denylist_filename)
-+        self.__xkb_info = GnomeDesktop.XkbInfo()
-+
-+    def read_simple(self):
-+        simple_file = open(self.__simple_filename)
-+        self.__simple_contents = simple_file.read()
-+        simple_file.close()
-+
-+    def read_denylist(self):
-+        denylist_file = open(self.__denylist_filename)
-+        self.__denylist_lines = denylist_file.readlines()
-+        denylist_file.close()
-+        l = []
-+        for line in self.__denylist_lines:
-+            line = line.rstrip()
-+            if len(line) == 0:
-+                continue
-+            if not line.startswith('#'):
-+                l.append(line)
-+        self.__denylist_lines = l
-+
-+    def __check_known_issue_engine_name(self, desc):
-+        reason = None
-+        if desc == 'xkb:in:eng:eng':
-+            wrong_desc = 'xkb:in:eng:en'
-+            simple_res = re.search(wrong_desc, self.__simple_contents)
-+            if simple_res != None:
-+                reason = '# Info: xkeyboard-config 2.42 has incorrect %s ' \
-+                         "but it's fixed in 2.44" % wrong_desc
-+        return reason
-+
-+    def test_simple_with_gnome(self):
-+        errnum = 0
-+        for layout_variant in self.__xkb_info.get_all_layouts():
-+            languages = self.__xkb_info.get_languages_for_layout(layout_variant)
-+            layouts = layout_variant.split('+')
-+            if len(languages) == 0:
-+                if self.__option_debug:
-+                    print('# Info: No language: %s' % layout_variant)
-+                continue
-+            if len(layouts) >= 2:
-+                desc = 'xkb:%s:%s:%s' % (layouts[0], layouts[1], languages[0])
-+            else:
-+                desc = 'xkb:%s::%s' % (layouts[0], languages[0])
-+            if self.__option_debug:
-+                print('# Debug: Test', layout_variant, languages, desc)
-+            simple_res = re.search(desc, self.__simple_contents)
-+            if simple_res == None:
-+                denylist_res = None
-+                for line in self.__denylist_lines:
-+                    denylist_res = re.match(line, desc)
-+                    if denylist_res != None:
-+                        break
-+                if denylist_res == None:
-+                    reason = self.__check_known_issue_engine_name(desc)
-+                    if reason != None:
-+                        if self.__option_debug:
-+                            print(reason)
-+                    else:
-+                        errnum = errnum + 1
-+                        print('# Error: not found: %s' % desc, file=sys.stderr)
-+                elif self.__option_debug:
-+                    print('# Info: %s in denylist' % desc)
-+        return errnum
-+
-+
-+def main():
-+    TestGnomeDesktop.parse_args()
-+    obj = TestGnomeDesktop()
-+    obj.read_simple()
-+    obj.read_denylist()
-+    errnum = obj.test_simple_with_gnome()
-+    if errnum == 0:
-+        print('Succeeded.')
-+    else:
-+        print('Failed. %s errors found.' % errnum, file=sys.stderr)
-+        sys.exit(1)
-+
-+main()
--- 
-2.49.0
-
-From b0ef1d79b7ae1289823380a194382372990ef333 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Thu, 5 Jun 2025 19:45:56 +0900
-Subject: [PATCH 4/4] src/tests: Add ibus-keyval
-
-Test ibus_keyval_to_unicode() and ibus_unicode_to_keyval()
-
-BUG=https://github.com/ibus/ibus/issues/2748
----
- src/tests/Makefile.am   | 10 +++++-
- src/tests/ibus-keyval.c | 74 +++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 83 insertions(+), 1 deletion(-)
- create mode 100644 src/tests/ibus-keyval.c
-
-diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
-index 628bef5f..125bdb26 100644
---- a/src/tests/Makefile.am
-+++ b/src/tests/Makefile.am
-@@ -3,7 +3,7 @@
- # ibus - The Input Bus
- #
- # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
--# Copyright (c) 2015-2024 Takao Fujiwara <takao.fujiwara1@gmail.com>
-+# Copyright (c) 2015-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
- # Copyright (c) 2007-2018 Red Hat, Inc.
- #
- # This library is free software; you can redistribute it and/or
-@@ -79,6 +79,10 @@ TESTS_C += ibus-keypress
- endif
- endif
- 
-+if ENABLE_GTK4
-+TESTS_C += ibus-keyval
-+endif
-+
- TESTS_ENVIRONMENT = \
-     top_builddir=$(top_builddir) \
-     top_srcdir=$(top_srcdir) \
-@@ -224,6 +228,10 @@ ibus_keypress_LDADD = $(prog_ldadd) @GTK3_LIBS@ @XTEST_LIBS@
- endif
- endif
- 
-+ibus_keyval_SOURCES = ibus-keyval.c
-+ibus_keyval_CFLAGS = @GTK4_CFLAGS@
-+ibus_keyval_LDADD = $(prog_ldadd) @GTK4_LIBS@
-+
- ibus_registry_SOURCES = ibus-registry.c
- ibus_registry_LDADD = $(prog_ldadd)
- 
-diff --git a/src/tests/ibus-keyval.c b/src/tests/ibus-keyval.c
-new file mode 100644
-index 00000000..fd2afbbb
---- /dev/null
-+++ b/src/tests/ibus-keyval.c
-@@ -0,0 +1,74 @@
-+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
-+
-+#include <config.h>
-+
-+#include <gtk/gtk.h>
-+#include <ibus.h>
-+
-+#ifdef ENABLE_NLS
-+#include <locale.h>
-+#endif
-+
-+#ifndef MAX
-+#define MAX(a, b) (a) >= (b) ? (a) : (b)
-+#endif
-+
-+#define MAX_KEYVAL 0x00FFFFFF
-+#define MAX_UNICODE 0x0010FFFF
-+
-+
-+static gboolean
-+test_keyval (void)
-+{
-+    guint si, bi;
-+    gunichar ibus_uch, gdk_uch;
-+
-+    for (si = 0; si < MAX_KEYVAL; ++si) {
-+        ibus_uch  = ibus_keyval_to_unicode (si);
-+        gdk_uch = gdk_keyval_to_unicode (si);
-+        g_assert_cmpuint (ibus_uch, ==, gdk_uch);
-+
-+        bi = si | 0x01000000;
-+        ibus_uch  = ibus_keyval_to_unicode (bi);
-+        gdk_uch = gdk_keyval_to_unicode (bi);
-+        g_assert_cmpuint (ibus_uch, ==, gdk_uch);
-+
-+        if (!(si % 0x100000))
-+            g_message ("0x%08X/0x%08X is done", si, MAX_KEYVAL);
-+    }
-+    return TRUE;
-+}
-+
-+
-+static gboolean
-+test_unicode (void)
-+{
-+    gunichar i;
-+    guint ibus_key, gdk_key;
-+    for (i = 0; i < MAX_UNICODE; ++i) {
-+        ibus_key = ibus_unicode_to_keyval (i);
-+        gdk_key = gdk_unicode_to_keyval (i);
-+        g_assert_cmpuint (ibus_key, ==, gdk_key);
-+        if (!(i % 0x100000))
-+            g_message ("0x%08X/0x%08X is done", i, MAX_UNICODE);
-+    }
-+    return TRUE;
-+}
-+
-+
-+int
-+main (int argc, char *argv[])
-+{
-+#if !GTK_CHECK_VERSION (4, 0, 0)
-+    g_message ("The latest GTK 4 is needed for this test case.");
-+    return 77;
-+#else
-+#ifdef ENABLE_NLS
-+    setlocale (LC_ALL, "");
-+#endif
-+    test_keyval ();
-+    g_message ("----------");
-+    test_unicode ();
-+    return EXIT_SUCCESS;
-+#endif
-+}
--- 
-2.49.0
-
-From 219386a67501e1a0fbbe45b28d5ef57e12a3c7d6 Mon Sep 17 00:00:00 2001
-From: matiwari <matiwari@redhat.com>
-Date: Thu, 9 Jan 2025 12:17:26 +0530
-Subject: [PATCH 1/4] ui/gtk3: Append non-glyph characters at last order for partial annotations
-
-BUG=rhbz#2267613
----
- ui/gtk3/emojier.vala | 106 +++++++++++++++++++++++++++++++++++--------
- 1 file changed, 87 insertions(+), 19 deletions(-)
-
-diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
-index 8c527eca..cd60fc08 100644
---- a/ui/gtk3/emojier.vala
-+++ b/ui/gtk3/emojier.vala
-@@ -165,6 +165,53 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-                 set_label(text);
-         }
-     }
-+    /**
-+     * ECheckVisibleLabel:
-+     * Create a label with the Pango context for the font glyph checking.
-+     */
-+    private class ECheckVisibleLabel : EWhiteLabel {
-+        private Pango.Layout layout;
-+        public ECheckVisibleLabel(string text = "") {
-+            GLib.Object(
-+                name : "IBusEmojierCheckVisibleLabel"
-+            );
-+            if (text != "")
-+                set_label(text);
-+            var pango_context = get_pango_context();
-+            layout = new Pango.Layout(pango_context);
-+            var font_desc =
-+                    Pango.FontDescription.from_string(m_emoji_font_family);
-+            layout.set_font_description(font_desc);
-+        }
-+        public bool is_glyph_visible(string emoji) {
-+            string cleaned_emoji = emoji
-+                .replace("\uFE0E", "")
-+                .replace("\uFE0F", "");
-+            if (cleaned_emoji == "")
-+                return false;
-+            layout.set_text(cleaned_emoji, -1);
-+            unowned Pango.LayoutLine? line = layout.get_line_readonly(0);
-+            if (line == null)
-+                return false;
-+            Pango.Rectangle ink_rect;
-+            Pango.Rectangle logical_rect;
-+            line.get_pixel_extents(out ink_rect, out logical_rect);
-+            if (ink_rect.width <= 0 || ink_rect.height <= 0)
-+                return false;
-+            // Check if single glyph is available for single characters
-+            if (cleaned_emoji.char_count() == 1) {
-+                unowned GLib.SList<Pango.GlyphItem>? runs = line.runs;
-+                if (runs != null && runs.length() == 1) {
-+                    var run = runs.data;
-+                    var font = run.item.analysis.font;
-+                    unichar ch = cleaned_emoji.get_char();
-+                    if (!font.has_char(ch))
-+                        return false;
-+                }
-+            }
-+            return true;
-+        }
-+    }
-     private class EPaddedLabel : Gtk.Label {
-         public EPaddedLabel(string          text,
-                             Gtk.Align       align) {
-@@ -1268,12 +1315,30 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-     }
- 
- 
-+    private delegate void CheckGlyph(string                  emoji,
-+                                     ref GLib.SList<string>? emojis,
-+                                     bool                    do_sort);
-+
-     private GLib.SList<string>?
-     lookup_emojis_from_annotation(string annotation) {
-         GLib.SList<string>? total_emojis = null;
-+        GLib.SList<string>? non_glyph_emojis = null;
-         unowned GLib.SList<string>? sub_emojis = null;
-         unowned GLib.SList<unichar>? sub_exact_unicodes = null;
-         unowned GLib.SList<unichar>? sub_unicodes = null;
-+        var label = new ECheckVisibleLabel();
-+        // valac warning for inner func: local functions are experimental
-+        CheckGlyph check_if_non_glyph_emojis = (emoji, ref emojis, do_sort) => {
-+            if (label.is_glyph_visible(emoji)) {
-+                if (do_sort)
-+                    emojis.insert_sorted(emoji, GLib.strcmp);
-+                else
-+                    emojis.append(emoji);
-+            } else if (non_glyph_emojis.find_custom(emoji,
-+                                                    GLib.strcmp) == null) {
-+                non_glyph_emojis.append(emoji);
-+            }
-+        };
-         int length = annotation.length;
-         if (m_has_partial_match && length >= m_partial_match_length) {
-             GLib.SList<string>? sorted_emojis = null;
-@@ -1303,49 +1368,52 @@ public class IBusEmojier : Gtk.ApplicationWindow {
-                 sub_emojis = m_annotation_to_emojis_dict.lookup(key);
-                 foreach (unowned string emoji in sub_emojis) {
-                     if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
--                        sorted_emojis.insert_sorted(emoji, GLib.strcmp);
-+                        check_if_non_glyph_emojis(emoji,
-+                                                  ref sorted_emojis,
-+                                                  true);
-                     }
-                 }
-             }
-             foreach (string emoji in sorted_emojis) {
--                if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
-+                if (total_emojis.find_custom(emoji, GLib.strcmp) == null)
-                     total_emojis.append(emoji);
--                }
-             }
-         } else {
-             sub_emojis = m_annotation_to_emojis_dict.lookup(annotation);
-             foreach (unowned string emoji in sub_emojis)
--                total_emojis.append(emoji);
-+                check_if_non_glyph_emojis(emoji, ref total_emojis, false);
-         }
-         sub_exact_unicodes = m_name_to_unicodes_dict.lookup(annotation);
-         foreach (unichar code in sub_exact_unicodes) {
-             string ch = code.to_string();
--            if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
--                total_emojis.append(ch);
--            }
-+            if (total_emojis.find_custom(ch, GLib.strcmp) == null)
-+                check_if_non_glyph_emojis(ch, ref total_emojis, false);
-         }
-         if (length >= m_partial_match_length) {
-             GLib.SList<string>? sorted_unicodes = null;
-             foreach (unowned string key in m_name_to_unicodes_dict.get_keys()) {
--                bool matched = false;
--                if (key.index_of(annotation) >= 0)
--                        matched = true;
--                if (!matched)
--                    continue;
--                sub_unicodes = m_name_to_unicodes_dict.lookup(key);
--                foreach (unichar code in sub_unicodes) {
--                    string ch = code.to_string();
--                    if (sorted_unicodes.find_custom(ch, GLib.strcmp) == null) {
--                        sorted_unicodes.insert_sorted(ch, GLib.strcmp);
-+                if (key.index_of(annotation) >= 0) {
-+                    sub_unicodes = m_name_to_unicodes_dict.lookup(key);
-+                    foreach (unichar code in sub_unicodes) {
-+                        string ch = code.to_string();
-+                        if (sorted_unicodes.find_custom(ch,
-+                                                        GLib.strcmp) == null) {
-+                            check_if_non_glyph_emojis(ch,
-+                                                      ref sorted_unicodes,
-+                                                      true);
-+                        }
-                     }
-                 }
-             }
-             foreach (string ch in sorted_unicodes) {
--                if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
-+                if (total_emojis.find_custom(ch, GLib.strcmp) == null)
-                     total_emojis.append(ch);
--                }
-             }
-         }
-+        foreach (string emoji in non_glyph_emojis) {
-+            if (total_emojis.find_custom(emoji, GLib.strcmp) == null)
-+                total_emojis.append(emoji);
-+        }
-         if (!m_loaded_unicode && m_unicode_deserialize_unicode_signal_id == 0) {
-             m_unicode_deserialize_unicode_signal_id =
-                     m_unicode_progress_object.deserialize_unicode.connect(
--- 
-2.49.0
-
-From f9592f30a2c2ac9f3b331eddf00845d9584e3bdc Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Fri, 13 Jun 2025 15:57:37 +0900
-Subject: [PATCH 2/4] ui/gtk3: Fix PageUp/PageDown buttons with hidding candidate popup
-
-Gtk.Widget.show_all() changes the visibilities of the child widgets
-and it's not good in case that the parent visibility depends on the
-visibilities of child widgets. Also using no_show_all property could
-introduce the more complicated logic to the candidate popup.
-
-BUG=https://github.com/ibus/ibus/issues/2757
-Fixes: https://github.com/ibus/ibus/commit/6ac6188
-Fixes: https://github.com/ibus/ibus/commit/d5e6e71
----
- ui/gtk3/candidatepanel.vala | 7 +++++--
- ui/gtk3/panel.vala          | 4 ++--
- 2 files changed, 7 insertions(+), 4 deletions(-)
-
-diff --git a/ui/gtk3/candidatepanel.vala b/ui/gtk3/candidatepanel.vala
-index a4137f05..3cc2c086 100644
---- a/ui/gtk3/candidatepanel.vala
-+++ b/ui/gtk3/candidatepanel.vala
-@@ -214,7 +214,6 @@ public class CandidatePanel : Gtk.Box{
-         m_set_preedit_text_id =
-                 Timeout.add(100,
-                             () => {
--                                //warning("test set_preedit_text_real");
-                                 m_set_preedit_text_id = 0;
-                                 set_preedit_text_real(text, cursor);
-                                 return Source.REMOVE;
-@@ -480,7 +479,11 @@ public class CandidatePanel : Gtk.Box{
-     }
- 
-     public new void show() {
--        m_toplevel.show_all();
-+        // m_toplevel.show_all() changes m_candidate_area.get_visible()
-+        // in update_real() so show() is just used. Using no_show_all
-+        // property for m_candidate_area would introduce the more
-+        // complicated logic.
-+        m_toplevel.show();
-     }
- 
-     public new void hide() {
-diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
-index d65ffbb3..61ee2e43 100644
---- a/ui/gtk3/panel.vala
-+++ b/ui/gtk3/panel.vala
-@@ -218,7 +218,7 @@ class Panel : IBus.PanelService {
- 
- #if USE_GDK_WAYLAND
-     private CandidatePanel get_active_candidate_panel() {
--        if (m_wayland_object_path == null) {
-+        if (m_is_wayland && m_wayland_object_path == null) {
-             if (m_candidate_panel_x11 == null) {
-                 m_candidate_panel_x11 = candidate_panel_new(true);
-                 set_use_glyph_from_engine_lang();
-@@ -233,7 +233,7 @@ class Panel : IBus.PanelService {
-     }
- 
-     private Switcher get_active_switcher() {
--        if (m_wayland_object_path == null) {
-+        if (m_is_wayland && m_wayland_object_path == null) {
-             if (m_switcher_x11 == null)
-                 m_switcher_x11 = switcher_new(true);
-             return m_switcher_x11;
--- 
-2.49.0
-
-From 152a58b5e90d1b6a94b1c09cd5778080e0762949 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Sat, 14 Jun 2025 15:15:58 +0900
-Subject: [PATCH 3/4] configure: Add warning to run `make -C ui/gtk3 maintainer-clean-generic`
-
-When users specify --disable-appindicator, --disable-xim,
---disable-gtk3-wayland, --disable-emoji-dict options for configure,
-the behavior effects VALA files in ibus/ui/gtk3 and need to
-regenerate C files but many users don't know the steps so added
-a warning in the configure output to inform to run
-`make -C ui/gtk3 maintainer-clean-generic` of the users.
-
-BUG=https://github.com/ibus/ibus/issues/2767
----
- configure.ac | 29 ++++++++++++++++++++++++++++-
- 1 file changed, 28 insertions(+), 1 deletion(-)
-
-diff --git a/configure.ac b/configure.ac
-index a5e4a44c..8a2b32d7 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -889,6 +889,30 @@ AC_SUBST(XKBCONFIG_BASE)
- 
- AC_SUBST([GDBUS_CODEGEN], [`$PKG_CONFIG --variable gdbus_codegen gio-2.0`])
- 
-+OUTPUT_TAIL=''
-+HAS_OUTPUT_TAIL=0
-+AS_IF([test x"$enable_ui" != x"no" &&
-+       test -f "$ac_confdir/ui/gtk3/application.c"
-+      ],
-+      [AS_IF([test x"$enable_appindicator" = x"no" ||
-+              test x"$enable_xim" = x"no" ||
-+              test x"$enable_gdk3_wayland" != x"yes" ||
-+              test x"$enable_emoji_dict" == x"no"
-+             ],
-+             [OUTPUT_TAIL="$OUTPUT_TAIL
-+You invoked the \`configure\` script with either disabled appinicator, xim,
-+gdk3_wayland or emoji_dict option so you have to run
-+\`make -C ui/gtk3 maintainer-clean-generic\` to regenerate C files with VALA
-+before run \`make\`.
-+"
-+              HAS_OUTPUT_TAIL=1
-+             ],
-+             [])
-+      ],
-+      []
-+)
-+
-+
- # OUTPUT files
- AC_CONFIG_FILES([
- po/Makefile.in
-@@ -992,4 +1016,7 @@ Build options:
-   Run test cases                $enable_tests
-   Install tests                 $enable_install_tests
- ])
--
-+AS_IF([test $HAS_OUTPUT_TAIL -eq 1],
-+      [AC_MSG_WARN([$OUTPUT_TAIL])],
-+      []
-+)
--- 
-2.49.0
-
-From d6b95085ec6aeaeb5af9cc3d9872348ba359638e Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Wed, 25 Jun 2025 21:56:51 +0900
-Subject: [PATCH 4/4] autogen: Do not require gtk-doc
-
-Since the license of gtk-doc.make and m4/gtk-doc.m4 is GPL but not LGPL,
-IBus does not save these files but add a dummy file m4/gtk-doc-dummy.m4.
-Now you can run `env GTKDOCIZE=echo ./autogen.sh --disable-gtk-doc`.
-
-BUG=https://github.com/ibus/ibus/issues/2770
----
- autogen.sh | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++----
- 1 file changed, 51 insertions(+), 4 deletions(-)
-
-diff --git a/autogen.sh b/autogen.sh
-index cde8aaef..1ae2ccc4 100755
---- a/autogen.sh
-+++ b/autogen.sh
-@@ -5,6 +5,7 @@
- : ${srcdir:=.}
- : ${SAVE_DIST_FILES:=0}
- : ${MAKE:=make}
-+: ${GTKDOCIZE:=gtkdocize}
- 
- olddir=$(pwd)
- # shellcheck disable=SC2016
-@@ -33,7 +34,12 @@ cd "$srcdir"
-     exit 1
- }
- 
--(test $(grep -q "^GTK_DOC_CHECK" configure.ac)) || {
-+CONFIGFLAGS="$@"
-+(test "x$NOCONFIGURE" = "x" ) &&
-+$(grep -q "^GTK_DOC_CHECK" configure.ac) && {
-+    # $WANT_GTK_DOC: If the source files require gtk-doc
-+    # Specify "--disable-gtk-doc" option for autogen.sh if you wish to disable
-+    # the gtk-doc builds.
-     WANT_GTK_DOC=1
-     FEDORA_PKG2="$FEDORA_PKG2 gtk-doc"
- }
-@@ -54,16 +60,57 @@ cd "$srcdir"
-     }
- }
- 
--CONFIGFLAGS="$@"
- (test "$#" = 0 -a "x$NOCONFIGURE" = "x" ) && {
-     echo "*** WARNING: I am going to run 'configure' with no arguments." >&2
-     echo "*** If you wish to pass any to it, please specify them on the" >&2
-     echo "*** '$0' command line." >&2
-     echo "" >&2
--    (test $WANT_GTK_DOC -eq 1) && CONFIGFLAGS="--enable-gtk-doc $@"
- }
- 
--(test $WANT_GTK_DOC -eq 1) && gtkdocize --copy
-+(test "x$NOCONFIGURE" = "x" ) && {
-+    $(echo "x$CONFIGFLAGS" | grep -q disable-gtk-doc) || {
-+        (test $WANT_GTK_DOC -eq 1) && CONFIGFLAGS="--enable-gtk-doc $@"
-+        (test -f ./m4/gtk-doc-dummy.m4) && $(rm ./m4/gtk-doc-dummy.m4)
-+    }
-+}
-+
-+# If $WANT_GTK_DOC is 1, gtkdocize should run basically. You could apply
-+# GTKDOCIZE=echo for the workaround if you disable to run dtkdocize likes
-+# autoreconf.
-+(test $WANT_GTK_DOC -eq 1) && $GTKDOCIZE --copy
-+
-+(test "x$NOCONFIGURE" = "x" ) &&
-+$(echo "x$CONFIGFLAGS" | grep -q disable-gtk-doc) &&
-+(test ! -f ./m4/gtk-doc.m4 ) && {
-+    # The license of gtk-doc.make and m4/gtk-doc.m4 is GPL but not LGPL
-+    # and IBus does not save the static files.
-+    cat > ./gtk-doc.make <<_EOF_MAKE
-+    EXTRA_DIST=
-+    CLEANFILES=
-+_EOF_MAKE
-+    cat > ./m4/gtk-doc-dummy.m4 <<_EOF_M4
-+AC_DEFUN([GTK_DOC_CHECK],
-+[
-+    have_gtk_doc=no
-+    AC_MSG_CHECKING([for gtk-doc])
-+    AC_MSG_RESULT($have_gtk_doc)
-+
-+    dnl enable/disable documentation building
-+    AC_ARG_ENABLE([gtk-doc],
-+    AS_HELP_STRING([--enable-gtk-doc],
-+                   [use gtk-doc to build documentation [[default=no]]]),,
-+    [enable_gtk_doc=no])
-+
-+    AC_MSG_CHECKING([whether to build gtk-doc documentation])
-+    AC_MSG_RESULT($enable_gtk_doc)
-+    AM_CONDITIONAL([ENABLE_GTK_DOC], [test "x$enable_gtk_doc" = "xyes"])
-+
-+    if test "x$enable_gtk_doc" = "xyes" && test "$have_gtk_doc" = "no"; then
-+        AC_MSG_ERROR([Something wrong in the build.])
-+    fi
-+])
-+_EOF_M4
-+}
- 
- ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4" REQUIRED_AUTOMAKE_VERSION=1.11 \
- autoreconf --verbose --force --install || exit 1
--- 
-2.49.0
-

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

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-31  2:08 Takao Fujiwara [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-05-31  2:09 [rpms/ibus] autotool: Delete upstreamed patches Takao Fujiwara
2026-05-31  2:09 Takao Fujiwara
2026-05-31  2:09 Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:08 [rpms/ibus] autotool: Delete Upstreamed patches Takao Fujiwara
2026-05-31  2:08 [rpms/ibus] autotool: Delete upstreamed patches Takao Fujiwara
2026-05-31  2:08 Takao Fujiwara
2026-05-31  2:07 Takao Fujiwara
2026-05-31  2:07 Takao Fujiwara
2026-05-31  2:07 Takao Fujiwara
2026-05-31  2:07 Takao Fujiwara

Reply instructions:

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

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

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

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

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

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

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