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
-
next 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