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] rawhide: Fix CapsLock between focus changes in Wayland
Date: Sun, 19 Jul 2026 10:41:24 GMT	[thread overview]
Message-ID: <178445768491.1.2420955277670842448.rpms-ibus-c99a155c56a1@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/ibus
            Branch : rawhide
            Commit : c99a155c56a1bc30b53cf1a5f01c18a500357ff9
            Author : Takao Fujiwara <tfujiwar@redhat.com>
            Date   : 2026-07-19T19:40:09+09:00
            Stats  : +1847/-1568 in 3 file(s)
            URL    : https://src.fedoraproject.org/rpms/ibus/c/c99a155c56a1bc30b53cf1a5f01c18a500357ff9?branch=rawhide

            Log:
            Fix CapsLock between focus changes in Wayland

- Merge ibus-2444009-wayland-xkb-lv-tilde.patch
- Fix typo in ibus-compose

---
diff --git a/ibus-2444009-wayland-xkb-lv-tilde.patch b/ibus-2444009-wayland-xkb-lv-tilde.patch
deleted file mode 100644
index 4071cac..0000000
--- a/ibus-2444009-wayland-xkb-lv-tilde.patch
+++ /dev/null
@@ -1,1565 +0,0 @@
-From 1686cb59c17744b07d8afefed0bda97d6c4930b8 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Wed, 17 Jun 2026 19:49:31 +0900
-Subject: [PATCH 1/4] client/wayland: Fix latch key in Latvian(tilde) keymap
-
-"lv(tilde)" keymap has `ISO_Level3_Latch` and there are several issues.
-1. KDE Wayland does not handle `ISO_Level3_Latch` keysym but outputs the
-   tilde even if the system keymap is "lv(tilde)". The previous change
-   0104486 fixes that issue in IBus side.
-2. There is a race when @IBusWaylandIM receives the system keymap from
-   the Wayland compositor and the user keymap from the IBus XKB
-   engine and the user keymap should not be overridden by the system one.
-3. @IBusWaylandIM can handle the compose keys only if `ISO_Level3_Latch`
-   is held pressing at present. But the latch state should be held when
-   the latch key is released but cleared when other keys are pressed.
-4. In case the system keymap is not "lv(tilde)", E.g. "us", the release
-   event of the virtual `ISO_Level3_Latch` key, the Wayland compositor
-   immediately sends to zero modifier state to clear the latch state.
-   So probably @IBusWaylandIM should not send the release event of the
-   latch key.
-5. XKB group ID can be switched by XKB option key likes `grp:toggle`
-   but clicking keyboard icon in KDE panel does not work and maybe
-   it's a bug in KDE compositor.
-6. Fix to clear modifiers with the key release of Shift + Latch key.
-7. Save CapsLock state
-
-Fixes: 0104486
-Closes: rhbz#2444009
----
- client/wayland/ibuswaylandim.c | 304 +++++++++++++++++++++++++++------
- 1 file changed, 248 insertions(+), 56 deletions(-)
-
-diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
-index d7d12d05..237f50ef 100644
---- a/client/wayland/ibuswaylandim.c
-+++ b/client/wayland/ibuswaylandim.c
-@@ -40,6 +40,9 @@
- #include "virtual-keyboard-unstable-v1-client-protocol.h"
- #include "ibuswaylandim.h"
- 
-+/* keysym & keycode should not be logged for the security issue. */
-+/* #define IBUS_LOG_SHOW_KEYSYM */
-+
- enum {
-     PROP_0 = 0,
-     PROP_BUS,
-@@ -146,6 +149,7 @@ struct _IBusWaylandIMPrivate
-     guint preedit_cursor_pos;
-     guint preedit_mode;
-     IBusModifierType modifiers;
-+    gboolean is_virtual_latch_state;
-     gboolean hiding_preedit_text;
-     IBusInputHints ibus_hints;
-     IBusInputPurpose ibus_purpose;
-@@ -203,10 +207,6 @@ static char _use_sync_mode = 1;
- 
- static guint wayland_im_signals[LAST_SIGNAL] = { 0 };
- 
--static void         input_method_deactivate
--                              (void                               *data,
--                               struct zwp_input_method_union      *input_method,
--                               struct zwp_input_method_context_v1 *context);
- static GObject     *ibus_wayland_im_constructor        (GType          type,
-                                                         guint          n_params,
-                                                         GObjectConstructParam
-@@ -228,6 +228,19 @@ static gboolean     ibus_wayland_im_post_key           (IBusWaylandIM *wlim,
-                                                         xkb_keysym_t   sym,
-                                                         gboolean
-                                                                       filtered);
-+static void         input_method_deactivate
-+                              (void                               *data,
-+                               struct zwp_input_method_union      *input_method,
-+                               struct zwp_input_method_context_v1 *context);
-+static void         input_method_keyboard_modifiers
-+                              (void                               *data,
-+                               struct zwp_keyboard_union          *keyboard,
-+                               uint32_t                            key_serial,
-+                               uint32_t
-+                                                                 mods_depressed,
-+                               uint32_t                            mods_latched,
-+                               uint32_t                            mods_locked,
-+                               uint32_t                            group);
- 
- 
- static char
-@@ -1129,7 +1142,14 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
-     keymap = create_user_xkb_keymap (priv->xkb_context, desc);
-     if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
-                                                        keymap)) {
--        xkb_keymap_unref (keymap);
-+        g_clear_pointer (&keymap, xkb_keymap_unref);
-+    }
-+    if (priv->verbose) {
-+        fprintf (priv->log, "New engine:%s keymap:%s state:%s\n",
-+                 ibus_engine_desc_get_name (desc),
-+                 keymap ? "TRUE" : "FALSE",
-+                 priv->key_user.state ? "TRUE" : "FALSE");
-+        fflush (priv->log);
-     }
-     g_object_unref (desc);
- }
-@@ -1253,6 +1273,7 @@ _get_seat_with_name (GPtrArray *seats,
-     return NULL;
- }
- 
-+
- static gboolean
- ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-                           uint32_t       key,
-@@ -1263,8 +1284,13 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
- {
-     IBusWaylandIMPrivate *priv;
-     IBusXkbKeymap *active_key;
-+    xkb_mod_mask_t mods_depressed = 0, new_mods_depressed = 0;
-+    xkb_mod_mask_t mods_locked;
-+    xkb_layout_index_t  group;
-+    xkb_keysym_t system_sym;
-     uint32_t code = key + 8;
-     uint32_t ch;
-+    gboolean clear_virtual_state = FALSE;
- 
-     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
-     priv = ibus_wayland_im_get_instance_private (wlim);
-@@ -1288,49 +1314,132 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-         active_key = &priv->key_user;
-     if (!active_key->state)
-         return FALSE;
-+    mods_depressed = xkb_state_serialize_mods (active_key->state,
-+                                               XKB_STATE_DEPRESSED |
-+                                               XKB_STATE_LATCHED);
-+    new_mods_depressed = mods_depressed;
-+    mods_locked = xkb_state_serialize_mods (active_key->state,
-+                                            XKB_STATE_MODS_LOCKED);
-+    /* XKB group layout is configured in the system keymap but not the
-+     * user keymap which always includes a single layout.
-+     */
-+    group = xkb_state_serialize_layout (priv->key_sys.state,
-+                                        XKB_STATE_LAYOUT_LOCKED);
-     if (!filtered) {
--#if 0
--        /* FIXME: Need to confirm any modifiers should be ignored. */
--        if (!ibus_accelerator_valid (
--                    sym,
--                    modifiers & IBUS_MODIFIER_MASK & ~IBUS_SHIFT_MASK)) {
--            filtered = TRUE;
--        }
--#else
-+        system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
-         switch (sym) {
--        case IBUS_ISO_Level2_Latch:
--        case IBUS_ISO_Level3_Latch:
--        case IBUS_ISO_Level5_Latch:
-+        case IBUS_KEY_ISO_Level2_Latch:
-             filtered = TRUE;
-             break;
-+        /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
-+         * Level3_Shift is caused by Alt key in lv(tilde) keymap.
-+         */
-+        case IBUS_KEY_ISO_Level3_Latch:
-+        case IBUS_KEY_ISO_Level3_Shift:
-+            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-+                new_mods_depressed |= active_key->mod5_mask;
-+                if (sym != system_sym)
-+                    priv->is_virtual_latch_state = TRUE;
-+                filtered = TRUE;
-+            }
-+            break;
-+        /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
-+         */
-+        case IBUS_KEY_ISO_Level5_Latch:
-+        case IBUS_KEY_ISO_Level5_Shift:
-+            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-+                new_mods_depressed |= active_key->mod3_mask;
-+                if (sym != system_sym)
-+                    priv->is_virtual_latch_state = TRUE;
-+                filtered = TRUE;
-+            }
-+            break;
-+        case IBUS_KEY_Shift_L:
-+        case IBUS_KEY_Shift_R:
-+            if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
-+                new_mods_depressed |= active_key->shift_mask;
-+            else
-+                new_mods_depressed &= ~active_key->shift_mask;
-+            break;
-         default:;
-         }
-+#ifdef IBUS_LOG_SHOW_KEYSYM
-+        if (priv->verbose) {
-+            fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
-+                                "state:%s filtered:%d\n",
-+                     G_STRFUNC,
-+                     key, sym, system_sym, modifiers,
-+                     state ? "press" : "release",
-+                     filtered);
-+            fflush (priv->log);
-+        }
- #endif
-     }
--    /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
--     * but forwards the key events to the focused application here.
--     * Because some applications treat the printable keys as control keys,
--     * E.g. game apps "hjkl" use the cursor move like VI mode.
--     * Unfortunately the Wayland input-method protocol does not provide
--     * the fallback logic after apps handle the key events like GTK3/2
--     * IM modules. But The input-method always should handles key events
--     * prior to apps.
--     */
--    if (!filtered && (state != WL_KEYBOARD_KEY_STATE_RELEASED) &&
--        !priv->use_sys_keymap) {
--        ch = xkb_state_key_get_utf32 (active_key->state, code);
--        if (!(modifiers & IBUS_MODIFIER_MASK & ~IBUS_SHIFT_MASK) &&
--            ch && ch != '\n' && ch != '\b' && ch != '\r' && ch != '\t' &&
--            ch != '\033' && ch != '\x7f') {
--            gchar buff[8] = { 0, };
--            buff[g_unichar_to_utf8 (ch, buff)] = '\0';
--            ibus_wayland_im_commit_text (wlim, buff);
--            filtered = TRUE;
-+    if (state != WL_KEYBOARD_KEY_STATE_RELEASED) {
-+        ch = ibus_keyval_to_unicode (sym);
-+        if (ch == 0 || g_unichar_iscntrl (ch))
-+            ch = xkb_state_key_get_utf32 (active_key->state, code);
-+/* No text with Control & Alt & Super keys */
-+#ifndef GDK_WINDOWING_QUARTZ
-+#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
-+    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
-+#else
-+#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
-+    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
-+#endif
-+        if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
-+             !g_unichar_iscntrl (ch)) || filtered) {
-+        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
-+         * but forwards the key events to the focused application here.
-+         * Because some applications treat the printable keys as control keys,
-+         * E.g. game apps "hjkl" use the cursor move like VI mode.
-+         * Unfortunately the Wayland input-method protocol does not provide
-+         * the fallback logic after apps handle the key events like GTK3/2
-+         * IM modules. But The input-method always should handles key events
-+         * prior to apps.
-+         */
-+            if (!filtered && !priv->use_sys_keymap) {
-+                gchar buff[8] = { 0, };
-+                buff[g_unichar_to_utf8 (ch, buff)] = '\0';
-+                ibus_wayland_im_commit_text (wlim, buff);
-+                filtered = TRUE;
-+            }
-+            /* If `filtered` is %TRUE, the keysym can be eaten for the compose
-+             * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
-+             * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)"
-+             * keymap. However if you configre multiple keymaps, AltGr-Shift
-+             * may produce the keymap switch although AltGr-Shift-V produces
-+             * `Greek_OMEGA`.
-+             * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
-+             */
-+            if (modifiers & IBUS_MOD3_MASK) {
-+                new_mods_depressed &= ~active_key->mod3_mask;
-+                clear_virtual_state = TRUE;
-+            }
-+            if (modifiers & IBUS_MOD5_MASK) {
-+                new_mods_depressed &= ~active_key->mod5_mask;
-+                clear_virtual_state = TRUE;
-+            }
-+        }
-+#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
-+    }
-+    if (priv->is_virtual_latch_state) {
-+        if (new_mods_depressed != mods_depressed) {
-+            input_method_keyboard_modifiers (wlim, NULL, 0,
-+                                             new_mods_depressed,
-+                                             0,
-+                                             mods_locked,
-+                                             group);
-         }
-+        if (clear_virtual_state)
-+            priv->is_virtual_latch_state = FALSE;
-+    }
-+    if (!priv->is_virtual_latch_state ||
-+        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
-+        xkb_state_update_key (active_key->state, code,
-+                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-+                              ? XKB_KEY_UP : XKB_KEY_DOWN);
-     }
--    xkb_state_update_key (active_key->state, code,
--                          (state == WL_KEYBOARD_KEY_STATE_RELEASED)
--                          ? XKB_KEY_UP : XKB_KEY_DOWN);
-     return filtered;
- }
- 
-@@ -1777,12 +1886,32 @@ input_method_keyboard_key (void                      *data,
-     /* xkb_key_get_syms() does not return the capital syms with Shift key. */
-     if (priv->key_sys.state)
-         event.sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
--    if (event.sym != IBUS_KEY_Multi_key && priv->key_user.state)
--        event.sym = xkb_state_key_get_one_sym (priv->key_user.state, code);
-+    switch (event.sym) {
-+    case IBUS_KEY_Multi_key:
-+    case IBUS_KEY_ISO_Next_Group:
-+        break;
-+    default:
-+        if (priv->key_user.state)
-+            event.sym = xkb_state_key_get_one_sym (priv->key_user.state, code);
-+    }
-     event.modifiers = priv->modifiers;
-     if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-         event.modifiers |= IBUS_RELEASE_MASK;
-     event.wlim = wlim;
-+#ifdef IBUS_LOG_SHOW_KEYSYM
-+    if (priv->verbose) {
-+        fprintf (priv->log, "%s serial:%u time:%u key:%u "
-+                            "sym:%x system_user:%x system_sym:%x "
-+                            "modifiers:%x state:%s\n",
-+                 G_STRFUNC,
-+                 key_serial, time, key,
-+                 event.sym,
-+                 xkb_state_key_get_one_sym (priv->key_user.state, code),
-+                 xkb_state_key_get_one_sym (priv->key_sys.state, code),
-+                 event.modifiers, state ? "press" : "release");
-+        fflush (priv->log);
-+    }
-+#endif
- 
-     key_event_check_repeat (wlim, &event);
-     switch (_use_sync_mode) {
-@@ -1796,6 +1925,29 @@ input_method_keyboard_key (void                      *data,
- }
- 
- 
-+/**
-+ * input_method_keyboard_modifiers:
-+ *
-+ * This is a common API of input_method_keyboard_modifiers_v1() and
-+ * input_method_keyboard_modifiers_v2().
-+ * If you configure multiple XKB layouts in the system with
-+ * /etc/vconsole.conf file, you can switch the active layout with
-+ * the XKB options and @group is changed with the shortcut key like
-+ * "grp:lalt_lshift_toggle" but If you click the keyboard icon in
-+ * Plasma KDE and change the active layout with GUI, @group is not
-+ * changed and I assume it's a bug [1].
-+ * Currently there are two cases of the combinations of the IBus keymap and
-+ * the system keymap supported by IBus:
-+ * 1. Always Same keymaps
-+ *    E.g. IBus keymap is "lv(tilde)" and system one is "lv(tilde)"
-+ * 2. System keymap is "US" and switch IBus keymaps with Super-space key
-+ *    E.g. IBus keymap is "lv(tilde)" and system one is "us"
-+ * So you should use both XKB option keys and IBus shortcutkeys to switch the
-+ * keymaps to change both XKB keymaps and IBus keymaps but you should not
-+ * click the keyboard icon in KDE.
-+ *
-+ * [1] https://bugs.kde.org/show_bug.cgi?id=518371
-+ */
- static void
- input_method_keyboard_modifiers (void                      *data,
-                                  struct zwp_keyboard_union *keyboard,
-@@ -1816,27 +1968,53 @@ input_method_keyboard_modifiers (void                      *data,
-         active_key = &priv->key_sys;
-     else
-         active_key = &priv->key_user;
--    if (priv->key_user.state) {
--        xkb_state_update_mask (priv->key_user.state, mods_depressed,
--                               mods_latched, mods_locked, 0, 0, group);
--    }
--    /* Pressing XKB group key likes Alt_R with grp:toggle calls
--     * input_method_keyboard_modifiers() with the updated `group`
--     * and need to update priv->key_sys.state to switch the XKB group
--     * in the system keymap.
--     *
--     * XKB group key can switch `group` but clicking the keyboard icon
--     * of KDE does not change `group` at present. Maybe a bug in the
--     * KDE Wayland compositor.
-+    /* Do not reset Latch modifiers by system in case the system keymap
-+     * has no the Latch key.
-+     * In case the user keymap is "lv(tilde)" and the system one is "us",
-+     * input_method_keyboard_modifiers() clears state of Level3_Latch key
-+     * immediately after the Level3_Latch key is released because the "us"
-+     * keymap does not have the latch keys.
-+     * The behavior is different in case both user and system keymaps are
-+     * "lv(tilde)".
-+     * So `is_virtual_latch_state` keeps the virtual state of the latch keys
-+     * not to override the state of the "us" keymap.
-      */
--    if (priv->key_sys.state) {
--        xkb_state_update_mask (priv->key_sys.state, mods_depressed,
--                               mods_latched, mods_locked, 0, 0, group);
-+    if (!priv->is_virtual_latch_state || key_serial == 0) {
-+        if (priv->key_user.state) {
-+            xkb_state_update_mask (priv->key_user.state, mods_depressed,
-+                                   mods_latched, mods_locked, 0, 0, group);
-+        }
-+        /* Pressing XKB group key likes Alt_R with grp:toggle calls
-+         * input_method_keyboard_modifiers() with the updated `group`
-+         * and need to update priv->key_sys.state to switch the XKB group
-+         * in the system keymap.
-+         *
-+         * XKB group key can switch `group` but clicking the keyboard icon
-+         * of KDE does not change `group` at present. Maybe a bug in the
-+         * KDE Wayland compositor.
-+         */
-+        if (priv->key_sys.state) {
-+            xkb_state_update_mask (priv->key_sys.state, mods_depressed,
-+                                   mods_latched, mods_locked, 0, 0, group);
-+        }
-     }
-     if (active_key->state) {
-+        /* CapsLock needs XKB_STATE_MODS_LOCKED */
-         mask = xkb_state_serialize_mods (active_key->state,
-                                          XKB_STATE_DEPRESSED |
--                                         XKB_STATE_LATCHED);
-+                                         XKB_STATE_LATCHED |
-+                                         XKB_STATE_MODS_LOCKED);
-+    }
-+    if (priv->verbose) {
-+        struct xkb_state *state = active_key->state;
-+        fprintf (priv->log, "Update modifiers serial:%u depress:%X latch:%X "
-+                             "lock:%X group:%X orig_depre:%X orig_latch:%X "
-+                             "orig_lock:%X\n",
-+                 key_serial, mods_depressed, mods_latched, mods_locked, group,
-+                 xkb_state_serialize_mods (state, XKB_STATE_DEPRESSED),
-+                 xkb_state_serialize_mods (state, XKB_STATE_LATCHED),
-+                 xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED));
-+        fflush (priv->log);
-     }
- 
-     priv->modifiers = 0;
-@@ -1863,6 +2041,9 @@ input_method_keyboard_modifiers (void                      *data,
-     if (mask & active_key->meta_mask)
-         priv->modifiers |= IBUS_META_MASK;
- 
-+    if (!key_serial)
-+        return;
-+
-     switch (priv->version) {
-     case INPUT_METHOD_V1:
-         zwp_input_method_context_v1_modifiers (priv->context, key_serial,
-@@ -2886,6 +3067,17 @@ ibus_wayland_im_constructor (GType                  type,
-                                                        keymap)) {
-         g_clear_pointer (&keymap, xkb_keymap_unref);
-     }
-+    if (priv->verbose) {
-+        if (!desc) {
-+            fprintf (priv->log, "Constructor has No global engine\n");
-+        } else {
-+            fprintf (priv->log, "Constructor engine %s keymap:%s state:%s\n",
-+                     ibus_engine_desc_get_name (desc),
-+                     keymap ? "TRUE" : "FALSE",
-+                     priv->key_user.state ? "TRUE" : "FALSE");
-+        }
-+        fflush (priv->log);
-+    }
-     ibus_bus_set_watch_ibus_signal (priv->ibusbus, TRUE);
-     g_signal_connect (priv->ibusbus, "global-engine-changed",
-                       G_CALLBACK (_bus_global_engine_changed_cb),
--- 
-2.53.0
-
-From 5fe640b170e09579b8b1955c5cad4aa4a6d1c464 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Mon, 15 Jun 2026 11:50:38 +0900
-Subject: [PATCH 2/4] client/wayland: Refactor ibus_wayland_im_post_key()
-
-Separate 3 functions.
-
-Closes: rhbz#2444009
----
- client/wayland/ibuswaylandim.c | 371 +++++++++++++++++++++------------
- 1 file changed, 243 insertions(+), 128 deletions(-)
-
-diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
-index 237f50ef..2cc2079b 100644
---- a/client/wayland/ibuswaylandim.c
-+++ b/client/wayland/ibuswaylandim.c
-@@ -367,6 +367,81 @@ ibus_wayland_im_commit_text (IBusWaylandIM *wlim,
- }
- 
- 
-+static gboolean
-+ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
-+                                  uint32_t        key,
-+                                  uint32_t        modifiers,
-+                                  uint32_t        state,
-+                                  xkb_keysym_t    sym,
-+                                  IBusXkbKeymap  *active_key,
-+                                  gboolean        filtered,
-+                                  xkb_mod_mask_t *new_mods_depressed,
-+                                  gboolean       *clear_virtual_state)
-+{
-+    IBusWaylandIMPrivate *priv;
-+    uint32_t code = key + 8;
-+    uint32_t ch;
-+
-+    g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), filtered);
-+    priv = ibus_wayland_im_get_instance_private (wlim);
-+
-+    if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-+        return filtered;
-+
-+    ch = ibus_keyval_to_unicode (sym);
-+    if (ch == 0 || g_unichar_iscntrl (ch))
-+        ch = xkb_state_key_get_utf32 (active_key->state, code);
-+
-+/* No text with Control & Alt & Super keys */
-+#ifndef GDK_WINDOWING_QUARTZ
-+#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
-+    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
-+#else
-+#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
-+    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
-+#endif
-+    if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
-+         !g_unichar_iscntrl (ch)) || filtered) {
-+        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
-+         * but forwards the key events to the focused application here.
-+         * Because some applications treat the printable keys as control keys,
-+         * E.g. game apps "hjkl" use the cursor move like VI mode.
-+         * Unfortunately the Wayland input-method protocol does not provide
-+         * the fallback logic after apps handle the key events like GTK3/2
-+         * IM modules. But The input-method always should handles key events
-+         * prior to apps.
-+         */
-+        if (!filtered && !priv->use_sys_keymap) {
-+            gchar buff[8] = { 0, };
-+            buff[g_unichar_to_utf8 (ch, buff)] = '\0';
-+            ibus_wayland_im_commit_text (wlim, buff);
-+            filtered = TRUE;
-+        }
-+        /* If `filtered` is %TRUE, the keysym can be eaten for the compose
-+         * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
-+         * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)" keymap.
-+         * However if you configre multiple keymaps, AltGr-Shift may produce
-+         * the keymap switch although AltGr-Shift-V produces `Greek_OMEGA`.
-+         * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
-+         */
-+        if (modifiers & IBUS_MOD3_MASK) {
-+            if (new_mods_depressed)
-+                *new_mods_depressed &= ~active_key->mod3_mask;
-+            if (clear_virtual_state)
-+                *clear_virtual_state = TRUE;
-+        }
-+        if (modifiers & IBUS_MOD5_MASK) {
-+            if (new_mods_depressed)
-+                *new_mods_depressed &= ~active_key->mod5_mask;
-+            if (clear_virtual_state)
-+                *clear_virtual_state = TRUE;
-+        }
-+    }
-+#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
-+    return filtered;
-+}
-+
-+
- static void
- ibus_wayland_im_key (IBusWaylandIM *wlim,
-                      uint32_t       key_serial,
-@@ -431,6 +506,149 @@ ibus_wayland_im_keysym (IBusWaylandIM *wlim,
- }
- 
- 
-+/**
-+ * ibus_wayland_im_update_virtual_depressed:
-+ *
-+ * If the IBus keymap is different from the compositor keymap,
-+ * IBus needs to maintain the modifiers state by itself to call
-+ * xkb_state_update_mask(), E.g. IBus keymap is "lv(tilde)" and the system
-+ * one is "us", because input_method_keyboard_modifiers()
-+ * is not called by the Wayland compositor in that case.
-+ * So this API updates @mods_depressed with ISO level3 and level5
-+ * latch and shift states, and send it to
-+ * ibus_wayland_im_update_virtual_xkb_state().
-+ */
-+static gboolean
-+ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-+                                          uint32_t        key,
-+                                          uint32_t        modifiers,
-+                                          uint32_t        state,
-+                                          xkb_keysym_t    sym,
-+                                          IBusXkbKeymap  *active_key,
-+                                          gboolean        filtered,
-+                                          xkb_mod_mask_t *mods_depressed)
-+{
-+    IBusWaylandIMPrivate *priv;
-+    uint32_t code = key + 8;
-+    xkb_mod_mask_t new_mods_depressed;
-+    xkb_keysym_t system_sym;
-+
-+    if (filtered)
-+        return filtered;
-+
-+    g_assert (IBUS_IS_WAYLAND_IM (wlim));
-+    g_assert (active_key);
-+    g_assert (mods_depressed);
-+
-+    priv = ibus_wayland_im_get_instance_private (wlim);
-+    new_mods_depressed = *mods_depressed;
-+
-+    if ((modifiers & ~IBUS_RELEASE_MASK ) != new_mods_depressed) {
-+        g_warning ("IBus modifiers %X is different from XKB depressed %X",
-+                   modifiers, new_mods_depressed);
-+    }
-+    system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
-+    switch (sym) {
-+    case IBUS_KEY_ISO_Level2_Latch:
-+        filtered = TRUE;
-+        break;
-+    /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
-+     * Level3_Shift is caused by Alt key in lv(tilde) keymap.
-+     */
-+    case IBUS_KEY_ISO_Level3_Latch:
-+    case IBUS_KEY_ISO_Level3_Shift:
-+        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-+            new_mods_depressed |= active_key->mod5_mask;
-+            if (sym != system_sym)
-+                priv->is_virtual_latch_state = TRUE;
-+            filtered = TRUE;
-+        }
-+        break;
-+    /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
-+     */
-+    case IBUS_KEY_ISO_Level5_Latch:
-+    case IBUS_KEY_ISO_Level5_Shift:
-+        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-+            new_mods_depressed |= active_key->mod3_mask;
-+            if (sym != system_sym)
-+                priv->is_virtual_latch_state = TRUE;
-+            filtered = TRUE;
-+        }
-+        break;
-+    case IBUS_KEY_Shift_L:
-+    case IBUS_KEY_Shift_R:
-+        if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
-+            new_mods_depressed |= active_key->shift_mask;
-+        else
-+            new_mods_depressed &= ~active_key->shift_mask;
-+        break;
-+    default:;
-+    }
-+#ifdef IBUS_LOG_SHOW_KEYSYM
-+    if (priv->verbose) {
-+        fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
-+                            "state:%s filtered:%d\n",
-+                 G_STRFUNC,
-+                 key, sym, system_sym, modifiers, state ? "press" : "release",
-+                 filtered);
-+        fflush (priv->log);
-+    }
-+#endif
-+    *mods_depressed = new_mods_depressed;
-+    return filtered;
-+}
-+
-+
-+/**
-+ * ibus_wayland_im_update_virtual_xkb_state:
-+ *
-+ * Update virtual IBus xkb_state by KeyPress and KeyRelease.
-+ */
-+static void
-+ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
-+                                          uint32_t       key,
-+                                          uint32_t       state,
-+                                          IBusXkbKeymap *active_key,
-+                                          xkb_mod_mask_t mods_depressed,
-+                                          xkb_mod_mask_t new_mods_depressed,
-+                                          gboolean       clear_virtual_state)
-+{
-+    IBusWaylandIMPrivate *priv;
-+    uint32_t code = key + 8;
-+    xkb_mod_mask_t mods_locked;
-+    xkb_layout_index_t  group;
-+
-+    g_assert (IBUS_IS_WAYLAND_IM (wlim));
-+
-+    priv = ibus_wayland_im_get_instance_private (wlim);
-+    mods_locked = xkb_state_serialize_mods (active_key->state,
-+                                            XKB_STATE_MODS_LOCKED);
-+    /* XKB group layout is configured in the system keymap but not the
-+     * user keymap which always includes a single layout.
-+     */
-+    group = xkb_state_serialize_layout (priv->key_sys.state,
-+                                        XKB_STATE_LAYOUT_LOCKED);
-+
-+    if (priv->is_virtual_latch_state) {
-+        if (new_mods_depressed != mods_depressed) {
-+            input_method_keyboard_modifiers (wlim, NULL, 0,
-+                                             new_mods_depressed,
-+                                             0,
-+                                             mods_locked,
-+                                             group);
-+        }
-+        if (clear_virtual_state)
-+            priv->is_virtual_latch_state = FALSE;
-+    }
-+    if (!priv->is_virtual_latch_state ||
-+        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
-+        xkb_state_update_key (active_key->state, code,
-+                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-+                              ? XKB_KEY_UP : XKB_KEY_DOWN);
-+    }
-+}
-+
-+
- static void
- _context_commit_text_cb (IBusInputContext *context,
-                          IBusText         *text,
-@@ -1284,12 +1502,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
- {
-     IBusWaylandIMPrivate *priv;
-     IBusXkbKeymap *active_key;
--    xkb_mod_mask_t mods_depressed = 0, new_mods_depressed = 0;
--    xkb_mod_mask_t mods_locked;
--    xkb_layout_index_t  group;
--    xkb_keysym_t system_sym;
--    uint32_t code = key + 8;
--    uint32_t ch;
-+    xkb_mod_mask_t mods_depressed, new_mods_depressed;
-     gboolean clear_virtual_state = FALSE;
- 
-     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
-@@ -1318,128 +1531,30 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-                                                XKB_STATE_DEPRESSED |
-                                                XKB_STATE_LATCHED);
-     new_mods_depressed = mods_depressed;
--    mods_locked = xkb_state_serialize_mods (active_key->state,
--                                            XKB_STATE_MODS_LOCKED);
--    /* XKB group layout is configured in the system keymap but not the
--     * user keymap which always includes a single layout.
--     */
--    group = xkb_state_serialize_layout (priv->key_sys.state,
--                                        XKB_STATE_LAYOUT_LOCKED);
--    if (!filtered) {
--        system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
--        switch (sym) {
--        case IBUS_KEY_ISO_Level2_Latch:
--            filtered = TRUE;
--            break;
--        /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
--         * Level3_Shift is caused by Alt key in lv(tilde) keymap.
--         */
--        case IBUS_KEY_ISO_Level3_Latch:
--        case IBUS_KEY_ISO_Level3_Shift:
--            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
--                new_mods_depressed |= active_key->mod5_mask;
--                if (sym != system_sym)
--                    priv->is_virtual_latch_state = TRUE;
--                filtered = TRUE;
--            }
--            break;
--        /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
--         */
--        case IBUS_KEY_ISO_Level5_Latch:
--        case IBUS_KEY_ISO_Level5_Shift:
--            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
--                new_mods_depressed |= active_key->mod3_mask;
--                if (sym != system_sym)
--                    priv->is_virtual_latch_state = TRUE;
--                filtered = TRUE;
--            }
--            break;
--        case IBUS_KEY_Shift_L:
--        case IBUS_KEY_Shift_R:
--            if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
--                new_mods_depressed |= active_key->shift_mask;
--            else
--                new_mods_depressed &= ~active_key->shift_mask;
--            break;
--        default:;
--        }
--#ifdef IBUS_LOG_SHOW_KEYSYM
--        if (priv->verbose) {
--            fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
--                                "state:%s filtered:%d\n",
--                     G_STRFUNC,
--                     key, sym, system_sym, modifiers,
--                     state ? "press" : "release",
--                     filtered);
--            fflush (priv->log);
--        }
--#endif
--    }
--    if (state != WL_KEYBOARD_KEY_STATE_RELEASED) {
--        ch = ibus_keyval_to_unicode (sym);
--        if (ch == 0 || g_unichar_iscntrl (ch))
--            ch = xkb_state_key_get_utf32 (active_key->state, code);
--/* No text with Control & Alt & Super keys */
--#ifndef GDK_WINDOWING_QUARTZ
--#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
--    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
--#else
--#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
--    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
--#endif
--        if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
--             !g_unichar_iscntrl (ch)) || filtered) {
--        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
--         * but forwards the key events to the focused application here.
--         * Because some applications treat the printable keys as control keys,
--         * E.g. game apps "hjkl" use the cursor move like VI mode.
--         * Unfortunately the Wayland input-method protocol does not provide
--         * the fallback logic after apps handle the key events like GTK3/2
--         * IM modules. But The input-method always should handles key events
--         * prior to apps.
--         */
--            if (!filtered && !priv->use_sys_keymap) {
--                gchar buff[8] = { 0, };
--                buff[g_unichar_to_utf8 (ch, buff)] = '\0';
--                ibus_wayland_im_commit_text (wlim, buff);
--                filtered = TRUE;
--            }
--            /* If `filtered` is %TRUE, the keysym can be eaten for the compose
--             * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
--             * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)"
--             * keymap. However if you configre multiple keymaps, AltGr-Shift
--             * may produce the keymap switch although AltGr-Shift-V produces
--             * `Greek_OMEGA`.
--             * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
--             */
--            if (modifiers & IBUS_MOD3_MASK) {
--                new_mods_depressed &= ~active_key->mod3_mask;
--                clear_virtual_state = TRUE;
--            }
--            if (modifiers & IBUS_MOD5_MASK) {
--                new_mods_depressed &= ~active_key->mod5_mask;
--                clear_virtual_state = TRUE;
--            }
--        }
--#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
--    }
--    if (priv->is_virtual_latch_state) {
--        if (new_mods_depressed != mods_depressed) {
--            input_method_keyboard_modifiers (wlim, NULL, 0,
--                                             new_mods_depressed,
--                                             0,
--                                             mods_locked,
--                                             group);
--        }
--        if (clear_virtual_state)
--            priv->is_virtual_latch_state = FALSE;
--    }
--    if (!priv->is_virtual_latch_state ||
--        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
--        xkb_state_update_key (active_key->state, code,
--                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
--                              ? XKB_KEY_UP : XKB_KEY_DOWN);
--    }
-+    filtered = ibus_wayland_im_update_virtual_depressed (wlim,
-+                                                         key,
-+                                                         modifiers,
-+                                                         state,
-+                                                         sym,
-+                                                         active_key,
-+                                                         filtered,
-+                                                         &new_mods_depressed);
-+    filtered = ibus_wayland_im_commit_key_event (wlim,
-+                                                 key,
-+                                                 modifiers,
-+                                                 state,
-+                                                 sym,
-+                                                 active_key,
-+                                                 filtered,
-+                                                 &new_mods_depressed,
-+                                                 &clear_virtual_state);
-+    ibus_wayland_im_update_virtual_xkb_state (wlim,
-+                                              key,
-+                                              state,
-+                                              active_key,
-+                                              mods_depressed,
-+                                              new_mods_depressed,
-+                                              clear_virtual_state);
-     return filtered;
- }
- 
--- 
-2.53.0
-
-From 36ab2bc5bf6881632d09d0be23b95401dfdea2a4 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Wed, 17 Jun 2026 19:51:08 +0900
-Subject: [PATCH 3/4] client/wayland: Refactor ISO_Level[3|5]_Shift state
-
-The states of ISO Shift keys should be cleared when the key is released
-against ISO Latch keys. E.g. "RALT" key in "lv(tilde)" keymap.
-
-The state of the "RALT" key is strange in "de(T3)" keymap, i.e.
-"ISO_Level3_Shift" keysym for the keypress and ISO_Level5_Latch keysm
-for keyrelease and IBus could not get the correct dead keys and
-clear the state. Now the "is_pressed_mod5" flag checks if the
-key is pressed and add a workaround.
-
-Closes: rhbz#2444009
----
- client/wayland/ibuswaylandim.c | 139 ++++++++++++++++++++++++++++++---
- 1 file changed, 127 insertions(+), 12 deletions(-)
-
-diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
-index 2cc2079b..571c7b05 100644
---- a/client/wayland/ibuswaylandim.c
-+++ b/client/wayland/ibuswaylandim.c
-@@ -64,6 +64,13 @@ typedef enum
-     INPUT_METHOD_V2,
- } IMProtocolVersion;
- 
-+typedef enum {
-+    IBUS_KEY_ISO_LEVEL_INVALID,
-+    IBUS_KEY_ISO_LEVEL_2,
-+    IBUS_KEY_ISO_LEVEL_3,
-+    IBUS_KEY_ISO_LEVEL_5
-+} IBusKeyIsoLevelValue;
-+
- struct zwp_input_method_context_union {
-     union {
-         struct zwp_input_method_context_v1 *context_v1;
-@@ -163,6 +170,8 @@ struct _IBusWaylandIMPrivate
- 
-     IBusXkbKeymap key_user;
-     IBusXkbKeymap key_sys;
-+    gboolean is_pressed_mod3;
-+    gboolean is_pressed_mod5;
- 
-     uint32_t im_serial;
-     int32_t repeat_rate;
-@@ -341,6 +350,28 @@ ibus_wayland_source_new (struct wl_display *display)
- }
- 
- 
-+static void
-+ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
-+{
-+    IBusWaylandIMPrivate *priv;
-+    xkb_layout_index_t  group = 0;
-+
-+    g_assert (IBUS_IS_WAYLAND_IM (wlim));
-+
-+    priv = ibus_wayland_im_get_instance_private (wlim);
-+    priv->is_virtual_latch_state = FALSE;
-+    priv->is_pressed_mod3 = FALSE;
-+    priv->is_pressed_mod5 = FALSE;
-+    if (priv->key_user.state && priv->key_sys.state) {
-+        group = xkb_state_serialize_layout (priv->key_sys.state,
-+                                            XKB_STATE_LAYOUT_LOCKED);
-+        input_method_keyboard_modifiers (wlim, NULL, 0, 0, 0, 0, group);
-+    } else {
-+        priv->modifiers = 0;
-+    }
-+}
-+
-+
- static void
- ibus_wayland_im_commit_text (IBusWaylandIM *wlim,
-                              const char    *str)
-@@ -526,7 +557,8 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-                                           xkb_keysym_t    sym,
-                                           IBusXkbKeymap  *active_key,
-                                           gboolean        filtered,
--                                          xkb_mod_mask_t *mods_depressed)
-+                                          xkb_mod_mask_t *mods_depressed,
-+                                          gboolean       *clear_virtual_state)
- {
-     IBusWaylandIMPrivate *priv;
-     uint32_t code = key + 8;
-@@ -539,6 +571,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-     g_assert (IBUS_IS_WAYLAND_IM (wlim));
-     g_assert (active_key);
-     g_assert (mods_depressed);
-+    g_assert (clear_virtual_state);
- 
-     priv = ibus_wayland_im_get_instance_private (wlim);
-     new_mods_depressed = *mods_depressed;
-@@ -554,25 +587,87 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-         break;
-     /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
-      * Level3_Shift is caused by Alt key in lv(tilde) keymap.
-+     * Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
-      */
-     case IBUS_KEY_ISO_Level3_Latch:
-     case IBUS_KEY_ISO_Level3_Shift:
--        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
--            new_mods_depressed |= active_key->mod5_mask;
--            if (sym != system_sym)
--                priv->is_virtual_latch_state = TRUE;
--            filtered = TRUE;
--        }
--        break;
--    /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
--     */
-     case IBUS_KEY_ISO_Level5_Latch:
-     case IBUS_KEY_ISO_Level5_Shift:
-         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
--            new_mods_depressed |= active_key->mod3_mask;
-+            switch (sym) {
-+            case IBUS_KEY_ISO_Level3_Latch:
-+            case IBUS_KEY_ISO_Level3_Shift:
-+                priv->is_pressed_mod5 = TRUE;
-+                new_mods_depressed |= active_key->mod5_mask;
-+                break;
-+            case IBUS_KEY_ISO_Level5_Latch:
-+            case IBUS_KEY_ISO_Level5_Shift:
-+                priv->is_pressed_mod3 = TRUE;
-+                new_mods_depressed |= active_key->mod3_mask;
-+                break;
-+            default:
-+                g_assert_not_reached ();
-+            }
-             if (sym != system_sym)
-                 priv->is_virtual_latch_state = TRUE;
-             filtered = TRUE;
-+        } else {
-+            IBusKeyIsoLevelValue current_level = IBUS_KEY_ISO_LEVEL_INVALID;
-+            gboolean *is_pressed_current_mod = NULL;
-+            gboolean *is_pressed_reverse_mod = NULL;
-+            xkb_mod_mask_t current_mode_mask = 0;
-+            xkb_mod_mask_t reverse_mode_mask = 0;
-+            switch (sym) {
-+            case IBUS_KEY_ISO_Level3_Latch:
-+            case IBUS_KEY_ISO_Level3_Shift:
-+                current_level = IBUS_KEY_ISO_LEVEL_3;
-+                is_pressed_current_mod = &priv->is_pressed_mod5;
-+                is_pressed_reverse_mod = &priv->is_pressed_mod3;
-+                current_mode_mask = active_key->mod5_mask;
-+                reverse_mode_mask = active_key->mod3_mask;
-+                break;
-+            case IBUS_KEY_ISO_Level5_Latch:
-+            case IBUS_KEY_ISO_Level5_Shift:
-+                current_level = IBUS_KEY_ISO_LEVEL_5;
-+                is_pressed_current_mod = &priv->is_pressed_mod3;
-+                is_pressed_reverse_mod = &priv->is_pressed_mod5;
-+                current_mode_mask = active_key->mod3_mask;
-+                reverse_mode_mask = active_key->mod5_mask;
-+                break;
-+            default:;
-+            }
-+            g_assert (current_level != IBUS_KEY_ISO_LEVEL_INVALID);
-+            g_assert (is_pressed_current_mod && is_pressed_reverse_mod);
-+            if (G_LIKELY (*is_pressed_current_mod)) {
-+                *is_pressed_current_mod = FALSE;
-+            } else if (*is_pressed_reverse_mod &&
-+                       (new_mods_depressed & active_key->mod3_mask)) {
-+                /* The unlikely case !priv->is_pressed_mod3 and
-+                 * priv->is_pressed_mod5 means that "de(T3)" keymap gives
-+                 * the pressed "Level3_Shift" keysym and
-+                 * the released "Level5_Latch" keysym for the key <RALT>.
-+                 * Maybe a bug in "de(T3)" so need to clear the MOD5 state
-+                 * by the pressed "Level3_Shift" keysym.
-+                 */
-+                *is_pressed_reverse_mod = FALSE;
-+                new_mods_depressed &= ~reverse_mode_mask;
-+                *clear_virtual_state = TRUE;
-+                g_debug ("Got a wrong released %s key without the "
-+                         "pressed one. Maybe a bug in XKB.",
-+                         current_level == IBUS_KEY_ISO_LEVEL_3 ?
-+                                 "Level3" : "Level5");
-+            }
-+            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
-+             * with the key <RALT>.
-+             */
-+            if ((current_level == IBUS_KEY_ISO_LEVEL_3 &&
-+                 sym == IBUS_KEY_ISO_Level3_Shift) ||
-+                (current_level == IBUS_KEY_ISO_LEVEL_5 &&
-+                 sym == IBUS_KEY_ISO_Level5_Shift)) {
-+                new_mods_depressed &= ~current_mode_mask;
-+                if (sym != system_sym)
-+                    clear_virtual_state = TRUE;
-+            }
-         }
-         break;
-     case IBUS_KEY_Shift_L:
-@@ -646,6 +741,23 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
-                               (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-                               ? XKB_KEY_UP : XKB_KEY_DOWN);
-     }
-+    if (priv->is_virtual_latch_state &&
-+        (new_mods_depressed != mods_depressed) &&
-+        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
-+        xkb_mod_mask_t new2_mods_depressed;
-+        new2_mods_depressed = xkb_state_serialize_mods (active_key->state,
-+                                                        XKB_STATE_DEPRESSED |
-+                                                        XKB_STATE_LATCHED);
-+        /* "de(T3)" keymap gives the pressed "Level3_Shift" keysym and
-+         * sets both MOD3(Level5) and MOD5(Level3) states after
-+         * xkb_state_update_key() is called with the keypress.
-+         * Maybe a bug in "de(T3)" and sets MOD5(Level3) again here.
-+         */
-+        if (G_UNLIKELY (new_mods_depressed != new2_mods_depressed)) {
-+            xkb_state_update_mask (active_key->state, new_mods_depressed,
-+                                   0, mods_locked, 0, 0, group);
-+        }
-+    }
- }
- 
- 
-@@ -1351,6 +1463,7 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
-     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
-     priv = ibus_wayland_im_get_instance_private (wlim);
-     desc = ibus_bus_get_global_engine (bus);
-+    ibus_wayland_im_reset_modifiers (wlim);
-     g_assert (desc);
-     g_assert (!g_strcmp0 (ibus_engine_desc_get_name (desc), engine_name));
-     /* Always update priv->key_user even if priv->use_sys_keymap is %FALSE
-@@ -1538,7 +1651,8 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-                                                          sym,
-                                                          active_key,
-                                                          filtered,
--                                                         &new_mods_depressed);
-+                                                         &new_mods_depressed,
-+                                                         &clear_virtual_state);
-     filtered = ibus_wayland_im_commit_key_event (wlim,
-                                                  key,
-                                                  modifiers,
-@@ -2411,6 +2525,7 @@ _create_input_context_done (GObject      *object,
-                                                            TRUE);
-         }
-         ibus_input_context_focus_in (priv->ibuscontext);
-+        ibus_wayland_im_reset_modifiers (wlim);
-         g_signal_emit (wlim,
-                        wayland_im_signals[IBUS_FOCUS_IN],
-                        0,
--- 
-2.53.0
-
-From 507c11b947f59be10b997ad4522024fcc2552845 Mon Sep 17 00:00:00 2001
-From: fujiwarat <takao.fujiwara1@gmail.com>
-Date: Mon, 15 Jun 2026 11:50:53 +0900
-Subject: [PATCH 4/4] client/wayland: Fix infinite dead_diaeresis with
- fr(ergol) keymap
-
-Typing the key <AD09> twice gives different keysyms between KeyPress
-and KeyRelease events and causes the reverse order of KeyPress and
-KeyRelease of the "dead_diaeresis" keysym.
-Now ibus-wayland checks the special order not to cause the key repeating.
-Also ibus-wayland releases the state of "ISO_Level5_Latch" with typing
-the key <AD09> twice.
-
-Closes: #2894
----
- client/wayland/ibuswaylandim.c | 245 +++++++++++++++++++++++----------
- 1 file changed, 172 insertions(+), 73 deletions(-)
-
-diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
-index 571c7b05..9c69439a 100644
---- a/client/wayland/ibuswaylandim.c
-+++ b/client/wayland/ibuswaylandim.c
-@@ -71,6 +71,12 @@ typedef enum {
-     IBUS_KEY_ISO_LEVEL_5
- } IBusKeyIsoLevelValue;
- 
-+typedef enum {
-+    IBUS_KEY_ISO_LEVEL_STATE_RELEASE,
-+    IBUS_KEY_ISO_LEVEL_STATE_SHIFT,
-+    IBUS_KEY_ISO_LEVEL_STATE_LATCH
-+} IBusKeyIsoLevelState;
-+
- struct zwp_input_method_context_union {
-     union {
-         struct zwp_input_method_context_v1 *context_v1;
-@@ -170,12 +176,14 @@ struct _IBusWaylandIMPrivate
- 
-     IBusXkbKeymap key_user;
-     IBusXkbKeymap key_sys;
--    gboolean is_pressed_mod3;
--    gboolean is_pressed_mod5;
-+    IBusKeyIsoLevelState iso_level3_state;
-+    IBusKeyIsoLevelState iso_level5_state;
- 
-     uint32_t im_serial;
-     int32_t repeat_rate;
-     int32_t repeat_delay;
-+    xkb_keysym_t pressed_dead_key;
-+    xkb_keysym_t released_dead_key_wo_press;
- 
-     GCancellable *cancellable;
- };
-@@ -360,8 +368,10 @@ ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
- 
-     priv = ibus_wayland_im_get_instance_private (wlim);
-     priv->is_virtual_latch_state = FALSE;
--    priv->is_pressed_mod3 = FALSE;
--    priv->is_pressed_mod5 = FALSE;
-+    priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
-+    priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
-+    priv->pressed_dead_key = 0;
-+    priv->released_dead_key_wo_press = 0;
-     if (priv->key_user.state && priv->key_sys.state) {
-         group = xkb_state_serialize_layout (priv->key_sys.state,
-                                             XKB_STATE_LAYOUT_LOCKED);
-@@ -407,7 +417,8 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
-                                   IBusXkbKeymap  *active_key,
-                                   gboolean        filtered,
-                                   xkb_mod_mask_t *new_mods_depressed,
--                                  gboolean       *clear_virtual_state)
-+                                  gboolean       *clear_virtual_state,
-+                                  gboolean       *is_invalid_key)
- {
-     IBusWaylandIMPrivate *priv;
-     uint32_t code = key + 8;
-@@ -416,9 +427,6 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
-     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), filtered);
-     priv = ibus_wayland_im_get_instance_private (wlim);
- 
--    if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
--        return filtered;
--
-     ch = ibus_keyval_to_unicode (sym);
-     if (ch == 0 || g_unichar_iscntrl (ch))
-         ch = xkb_state_key_get_utf32 (active_key->state, code);
-@@ -431,23 +439,28 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
- #  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
-     IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
- #endif
--    if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
--         !g_unichar_iscntrl (ch)) || filtered) {
--        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
--         * but forwards the key events to the focused application here.
--         * Because some applications treat the printable keys as control keys,
--         * E.g. game apps "hjkl" use the cursor move like VI mode.
--         * Unfortunately the Wayland input-method protocol does not provide
--         * the fallback logic after apps handle the key events like GTK3/2
--         * IM modules. But The input-method always should handles key events
--         * prior to apps.
--         */
--        if (!filtered && !priv->use_sys_keymap) {
--            gchar buff[8] = { 0, };
--            buff[g_unichar_to_utf8 (ch, buff)] = '\0';
--            ibus_wayland_im_commit_text (wlim, buff);
--            filtered = TRUE;
--        }
-+    if ((state == WL_KEYBOARD_KEY_STATE_RELEASED) ||
-+        (modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK)) {
-+        return filtered;
-+    }
-+#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
-+
-+    /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
-+     * but forwards the key events to the focused application here.
-+     * Because some applications treat the printable keys as control keys,
-+     * E.g. game apps "hjkl" use the cursor move like VI mode.
-+     * Unfortunately the Wayland input-method protocol does not provide
-+     * the fallback logic after apps handle the key events like GTK3/2
-+     * IM modules. But The input-method always should handles key events
-+     * prior to apps.
-+     */
-+    if (!filtered && !g_unichar_iscntrl (ch) && !priv->use_sys_keymap) {
-+        gchar buff[8] = { 0, };
-+        buff[g_unichar_to_utf8 (ch, buff)] = '\0';
-+        ibus_wayland_im_commit_text (wlim, buff);
-+        filtered = TRUE;
-+    }
-+    if (!g_unichar_iscntrl (ch) || IS_DEAD_KEY (sym)) {
-         /* If `filtered` is %TRUE, the keysym can be eaten for the compose
-          * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
-          * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)" keymap.
-@@ -456,19 +469,42 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
-          * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
-          */
-         if (modifiers & IBUS_MOD3_MASK) {
-+            /* With the "fr(ergol)" keymap, Typing the key <AD09> twice
-+             * produces the "ISO_Level5_Latch" keysym for the first KeyPress
-+             * and the "dead_diaeresis" keysym for the first KeyReleease
-+             * because the release key event has MOD3(level5) state.
-+             * And the second KeyPress has the "dead_diaeresis" keysym and
-+             * the second KeyRelease has the "ISO_Level5_Latch" keysym.
-+             * The latch state should be cleared with the dead keys.
-+             * I downgrade the latch state to the shift state here and
-+             * the shift state will be cleared by the released
-+             * "ISO_Level5_Latch" keysym but not the pressed dead keys
-+             * because xkb_state_update_key() will revert the MOD3(level5)
-+             * state of the depressed xkb_state_serialize_mods()
-+             * with the dead keys.
-+             */
-+            if (priv->iso_level5_state == IBUS_KEY_ISO_LEVEL_STATE_LATCH) {
-+                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
-+                if (is_invalid_key)
-+                    *is_invalid_key = TRUE;
-+            } else if (clear_virtual_state) {
-+                *clear_virtual_state = TRUE;
-+            }
-             if (new_mods_depressed)
-                 *new_mods_depressed &= ~active_key->mod3_mask;
--            if (clear_virtual_state)
--                *clear_virtual_state = TRUE;
-         }
-         if (modifiers & IBUS_MOD5_MASK) {
-+            if (priv->iso_level3_state == IBUS_KEY_ISO_LEVEL_STATE_LATCH) {
-+                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
-+                if (is_invalid_key)
-+                    *is_invalid_key = TRUE;
-+            } else if (clear_virtual_state) {
-+                *clear_virtual_state = TRUE;
-+            }
-             if (new_mods_depressed)
-                 *new_mods_depressed &= ~active_key->mod5_mask;
--            if (clear_virtual_state)
--                *clear_virtual_state = TRUE;
-         }
-     }
--#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
-     return filtered;
- }
- 
-@@ -558,7 +594,8 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-                                           IBusXkbKeymap  *active_key,
-                                           gboolean        filtered,
-                                           xkb_mod_mask_t *mods_depressed,
--                                          gboolean       *clear_virtual_state)
-+                                          gboolean       *clear_virtual_state,
-+                                          gboolean       *is_invalid_key)
- {
-     IBusWaylandIMPrivate *priv;
-     uint32_t code = key + 8;
-@@ -572,6 +609,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-     g_assert (active_key);
-     g_assert (mods_depressed);
-     g_assert (clear_virtual_state);
-+    g_assert (is_invalid_key);
- 
-     priv = ibus_wayland_im_get_instance_private (wlim);
-     new_mods_depressed = *mods_depressed;
-@@ -596,13 +634,19 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-             switch (sym) {
-             case IBUS_KEY_ISO_Level3_Latch:
-+                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_LATCH;
-+                new_mods_depressed |= active_key->mod5_mask;
-+                break;
-             case IBUS_KEY_ISO_Level3_Shift:
--                priv->is_pressed_mod5 = TRUE;
-+                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
-                 new_mods_depressed |= active_key->mod5_mask;
-                 break;
-             case IBUS_KEY_ISO_Level5_Latch:
-+                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_LATCH;
-+                new_mods_depressed |= active_key->mod3_mask;
-+                break;
-             case IBUS_KEY_ISO_Level5_Shift:
--                priv->is_pressed_mod3 = TRUE;
-+                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
-                 new_mods_depressed |= active_key->mod3_mask;
-                 break;
-             default:
-@@ -613,61 +657,70 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
-             filtered = TRUE;
-         } else {
-             IBusKeyIsoLevelValue current_level = IBUS_KEY_ISO_LEVEL_INVALID;
--            gboolean *is_pressed_current_mod = NULL;
--            gboolean *is_pressed_reverse_mod = NULL;
--            xkb_mod_mask_t current_mode_mask = 0;
--            xkb_mod_mask_t reverse_mode_mask = 0;
-+            IBusKeyIsoLevelState *current_state = NULL;
-+            IBusKeyIsoLevelState *reverse_state = NULL;
-             switch (sym) {
-             case IBUS_KEY_ISO_Level3_Latch:
-             case IBUS_KEY_ISO_Level3_Shift:
-                 current_level = IBUS_KEY_ISO_LEVEL_3;
--                is_pressed_current_mod = &priv->is_pressed_mod5;
--                is_pressed_reverse_mod = &priv->is_pressed_mod3;
--                current_mode_mask = active_key->mod5_mask;
--                reverse_mode_mask = active_key->mod3_mask;
-+                current_state = &priv->iso_level3_state;
-+                reverse_state = &priv->iso_level5_state;
-                 break;
-             case IBUS_KEY_ISO_Level5_Latch:
-             case IBUS_KEY_ISO_Level5_Shift:
-                 current_level = IBUS_KEY_ISO_LEVEL_5;
--                is_pressed_current_mod = &priv->is_pressed_mod3;
--                is_pressed_reverse_mod = &priv->is_pressed_mod5;
--                current_mode_mask = active_key->mod3_mask;
--                reverse_mode_mask = active_key->mod5_mask;
-+                current_state = &priv->iso_level5_state;
-+                reverse_state = &priv->iso_level3_state;
-                 break;
-             default:;
-             }
-             g_assert (current_level != IBUS_KEY_ISO_LEVEL_INVALID);
--            g_assert (is_pressed_current_mod && is_pressed_reverse_mod);
--            if (G_LIKELY (*is_pressed_current_mod)) {
--                *is_pressed_current_mod = FALSE;
--            } else if (*is_pressed_reverse_mod &&
--                       (new_mods_depressed & active_key->mod3_mask)) {
-+            g_assert (current_state && reverse_state);
-+            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
-+             * with the key <RALT>.
-+             *
-+             * Handle the "ISO_Level5_Latch" keysym in the "fr(ergol)" keymap.
-+             * Do not use keysym but `current_state` because the latch state
-+             * can be changed to the shift state with some key conditions.
-+             */
-+            if (*current_state == IBUS_KEY_ISO_LEVEL_STATE_SHIFT) {
-+                *current_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
-+                if (current_level == IBUS_KEY_ISO_LEVEL_3)
-+                    new_mods_depressed &= ~active_key->mod5_mask;
-+                else if (current_level == IBUS_KEY_ISO_LEVEL_5)
-+                    new_mods_depressed &= ~active_key->mod3_mask;
-+                else
-+                    g_assert_not_reached ();
-+                if (sym != system_sym)
-+                    *clear_virtual_state = TRUE;
-+            } else if (!*current_state && *reverse_state &&
-+                       (((current_level == IBUS_KEY_ISO_LEVEL_3) &&
-+                         (new_mods_depressed & active_key->mod3_mask)) ||
-+                        ((current_level == IBUS_KEY_ISO_LEVEL_5) &&
-+                         (new_mods_depressed & active_key->mod5_mask))
-+                       )) {
-                 /* The unlikely case !priv->is_pressed_mod3 and
-                  * priv->is_pressed_mod5 means that "de(T3)" keymap gives
-                  * the pressed "Level3_Shift" keysym and
-                  * the released "Level5_Latch" keysym for the key <RALT>.
--                 * Maybe a bug in "de(T3)" so need to clear the MOD5 state
--                 * by the pressed "Level3_Shift" keysym.
-+                 * This case can happen if the normal keysym and shift keysym
-+                 * are different.
-                  */
--                *is_pressed_reverse_mod = FALSE;
--                new_mods_depressed &= ~reverse_mode_mask;
-+                *reverse_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
-+                *is_invalid_key = TRUE;
-+                if (current_level == IBUS_KEY_ISO_LEVEL_3)
-+                    new_mods_depressed &= ~active_key->mod3_mask;
-+                else if (current_level == IBUS_KEY_ISO_LEVEL_5)
-+                    new_mods_depressed &= ~active_key->mod5_mask;
-+                else
-+                    g_assert_not_reached ();
-                 *clear_virtual_state = TRUE;
-                 g_debug ("Got a wrong released %s key without the "
--                         "pressed one. Maybe a bug in XKB.",
-+                         "pressed one. Maybe the next Shift state will "
-+                         "produce the delayed pressed one.",
-                          current_level == IBUS_KEY_ISO_LEVEL_3 ?
-                                  "Level3" : "Level5");
-             }
--            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
--             * with the key <RALT>.
--             */
--            if ((current_level == IBUS_KEY_ISO_LEVEL_3 &&
--                 sym == IBUS_KEY_ISO_Level3_Shift) ||
--                (current_level == IBUS_KEY_ISO_LEVEL_5 &&
--                 sym == IBUS_KEY_ISO_Level5_Shift)) {
--                new_mods_depressed &= ~current_mode_mask;
--                if (sym != system_sym)
--                    clear_virtual_state = TRUE;
--            }
-         }
-         break;
-     case IBUS_KEY_Shift_L:
-@@ -706,7 +759,8 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
-                                           IBusXkbKeymap *active_key,
-                                           xkb_mod_mask_t mods_depressed,
-                                           xkb_mod_mask_t new_mods_depressed,
--                                          gboolean       clear_virtual_state)
-+                                          gboolean       clear_virtual_state,
-+                                          gboolean       is_invalid_key)
- {
-     IBusWaylandIMPrivate *priv;
-     uint32_t code = key + 8;
-@@ -735,8 +789,9 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
-         if (clear_virtual_state)
-             priv->is_virtual_latch_state = FALSE;
-     }
--    if (!priv->is_virtual_latch_state ||
--        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
-+    if (G_LIKELY (!is_invalid_key) &&
-+        (!priv->is_virtual_latch_state ||
-+         (state != WL_KEYBOARD_KEY_STATE_RELEASED))) {
-         xkb_state_update_key (active_key->state, code,
-                               (state == WL_KEYBOARD_KEY_STATE_RELEASED)
-                               ? XKB_KEY_UP : XKB_KEY_DOWN);
-@@ -751,7 +806,8 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
-         /* "de(T3)" keymap gives the pressed "Level3_Shift" keysym and
-          * sets both MOD3(Level5) and MOD5(Level3) states after
-          * xkb_state_update_key() is called with the keypress.
--         * Maybe a bug in "de(T3)" and sets MOD5(Level3) again here.
-+         * FIXME: Should set `is_invalid_key` in this case of the "de(T3)"
-+         * keymap too not to call xkb_state_update_key() here?
-          */
-         if (G_UNLIKELY (new_mods_depressed != new2_mods_depressed)) {
-             xkb_state_update_mask (active_key->state, new_mods_depressed,
-@@ -1617,6 +1673,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-     IBusXkbKeymap *active_key;
-     xkb_mod_mask_t mods_depressed, new_mods_depressed;
-     gboolean clear_virtual_state = FALSE;
-+    gboolean is_invalid_key = FALSE;
- 
-     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
-     priv = ibus_wayland_im_get_instance_private (wlim);
-@@ -1652,7 +1709,8 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-                                                          active_key,
-                                                          filtered,
-                                                          &new_mods_depressed,
--                                                         &clear_virtual_state);
-+                                                         &clear_virtual_state,
-+                                                         &is_invalid_key);
-     filtered = ibus_wayland_im_commit_key_event (wlim,
-                                                  key,
-                                                  modifiers,
-@@ -1661,14 +1719,16 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
-                                                  active_key,
-                                                  filtered,
-                                                  &new_mods_depressed,
--                                                 &clear_virtual_state);
-+                                                 &clear_virtual_state,
-+                                                 &is_invalid_key);
-     ibus_wayland_im_update_virtual_xkb_state (wlim,
-                                               key,
-                                               state,
-                                               active_key,
-                                               mods_depressed,
-                                               new_mods_depressed,
--                                              clear_virtual_state);
-+                                              clear_virtual_state,
-+                                              is_invalid_key);
-     return filtered;
- }
- 
-@@ -2047,6 +2107,21 @@ key_event_check_repeat (IBusWaylandIM       *wlim,
-         g_clear_pointer (&repeating_event.ibus_object_path, g_free);
-         if (!priv->ibuscontext)
-             return FALSE;
-+        if (IS_DEAD_KEY (event->sym)) {
-+            /* With "fr(ergol)" keymap, in case that key <AD09> is typed
-+             * twice, the second pressed keysym is "dead_diaeresis" and
-+             * the second released keysym is "ISO_Level5_Latch".
-+             * The keysym "dead_diaeresis" should not be auto-repeated
-+             * in this case.
-+             */
-+            if (priv->released_dead_key_wo_press == event->sym) {
-+                priv->released_dead_key_wo_press = 0;
-+                return TRUE;
-+            }
-+            priv->pressed_dead_key = event->sym;
-+        } else {
-+            priv->released_dead_key_wo_press = 0;
-+        }
-         source = g_timeout_source_new (priv->repeat_delay);
-         g_source_attach (source, NULL);
-         g_source_unref (source);
-@@ -2060,6 +2135,30 @@ key_event_check_repeat (IBusWaylandIM       *wlim,
-         g_source_set_callback (source, _process_key_event_repeat_delay_cb,
-                                &repeating_event, NULL);
-     } else {
-+        if (event->sym != repeating_event.sym) {
-+            if (IS_DEAD_KEY (event->sym)) {
-+                if (event->sym == priv->pressed_dead_key) {
-+                    priv->pressed_dead_key = 0;
-+                } else if (!priv->pressed_dead_key) {
-+                    /* With "fr(ergol)" keymap, in case that key <AD09> is
-+                     * typed twice, the first pressed keysym is
-+                     * "ISO_Level5_Latch" and the first released keysym is
-+                     * "dead_diaeresis" with the MOD3(the level5) mask,
-+                     * the second pressed keysym is "dead_diaeresis" and
-+                     * the second released keysym is "ISO_Level5_Latch".
-+                     * So the first released "dead_diaeresis" is invalid and
-+                     * the second pressed "dead_diaeresis" should be released
-+                     * immediately.
-+                     */
-+                    priv->released_dead_key_wo_press = event->sym;
-+                }
-+            }
-+        } else if (IS_DEAD_KEY (repeating_event.sym)) {
-+            if (repeating_event.sym == priv->pressed_dead_key)
-+                priv->pressed_dead_key = 0;
-+        } else {
-+            priv->released_dead_key_wo_press = 0;
-+        }
-         if (repeating_event.repeat_rate_id) {
-             g_source_remove (repeating_event.repeat_rate_id);
-             repeating_event.repeat_rate_id = 0;
--- 
-2.53.0
-

diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index 9fd19c2..0e34d54 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -1,7 +1,7 @@
 From 44e22e5e7602bc79040df42cbfa836ba32da01ef Mon Sep 17 00:00:00 2001
 From: fujiwarat <takao.fujiwara1@gmail.com>
 Date: Thu, 16 Jul 2026 12:37:28 +0900
-Subject: [PATCH] src/tests: Fix CI issues in GNOME 50
+Subject: [PATCH 1/2] src/tests: Fix CI issues in GNOME 50
 
 - 100-ibus-ci.rules: Disable authentication dialogs in gnome-shell
 - ibus-compose:
@@ -704,3 +704,1843 @@ index 4e54420f..dee675cb 100755
 -- 
 2.54.0
 
+From 1a0d9a6bc3afe241838c97ca381e1bd0b376a2b5 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sun, 19 Jul 2026 18:10:01 +0900
+Subject: [PATCH 2/2] src/tests/ibus-compose: Fix typo
+
+Fixes: 44e22e5e
+---
+ meson.build              | 1 +
+ src/tests/ibus-compose.c | 4 ++--
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index 7cd906d7..c57b002d 100644
+--- a/meson.build
++++ b/meson.build
+@@ -391,6 +391,7 @@ install_emptydir(get_option('datadir') / 'ibus' / 'engine')
+ summary({
+   'Install prefix':                get_option('prefix'),
+   'CFLAGS':                        get_option('c_args'),
++  'LDFLAGS':                       get_option('c_link_args'),
+   'Build endianness':              endian,
+   'PYTHON':                        python.full_path(),
+   'Build PyGObject':               enable_pygobject,
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index c36b4c81..d0c573e5 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -144,7 +144,7 @@ take_screenshot (void)
+     dt = g_date_time_new_from_unix_utc (t);
+     g_return_if_fail (dt);
+ 
+-    while (FALSE) {
++    do {
+         prgname = g_path_get_basename (g_get_prgname ());
+         casename = g_path_get_basename (g_test_get_path ());
+         ibus_break_if_fail (prgname);
+@@ -173,7 +173,7 @@ take_screenshot (void)
+                        std_output ? std_output : "",
+                        std_error);
+         }
+-    }
++    } while (FALSE);
+ 
+     g_free (prgname);
+     g_free (casename);
+-- 
+2.54.0
+
+From 84aaf61fc56735c65eb9c4686aa773cb3b05faa2 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 29 Jun 2026 12:50:56 +0900
+Subject: [PATCH 1/5] client/wayland: Fix latch key in Latvian(tilde) keymap
+
+"lv(tilde)" keymap has `ISO_Level3_Latch` and there are several issues.
+1. KDE Wayland does not handle `ISO_Level3_Latch` keysym but outputs the
+   tilde even if the system keymap is "lv(tilde)". The previous change
+   0104486 fixes that issue in IBus side.
+2. There is a race when @IBusWaylandIM receives the system keymap from
+   the Wayland compositor and the user keymap from the IBus XKB
+   engine and the user keymap should not be overridden by the system one.
+3. @IBusWaylandIM can handle the compose keys only if `ISO_Level3_Latch`
+   is held pressing at present. But the latch state should be held when
+   the latch key is released but cleared when other keys are pressed.
+4. In case the system keymap is not "lv(tilde)", E.g. "us", the release
+   event of the virtual `ISO_Level3_Latch` key, the Wayland compositor
+   immediately sends to zero modifier state to clear the latch state.
+   So probably @IBusWaylandIM should not send the release event of the
+   latch key.
+5. XKB group ID can be switched by XKB option key likes `grp:toggle`
+   but clicking keyboard icon in KDE panel does not work and maybe
+   it's a bug in KDE compositor.
+6. Fix to clear modifiers with the key release of Shift + Latch key.
+7. Save CapsLock state
+
+Fixes: 0104486
+Fixes: #2869
+Closes: rhbz#2444009
+---
+ client/wayland/ibuswaylandim.c | 360 ++++++++++++++++++++++++++-------
+ 1 file changed, 285 insertions(+), 75 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index d7d12d05..95b330f8 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -40,6 +40,9 @@
+ #include "virtual-keyboard-unstable-v1-client-protocol.h"
+ #include "ibuswaylandim.h"
+ 
++/* keysym & keycode should not be logged for the security issue. */
++/* #define IBUS_LOG_SHOW_KEYSYM */
++
+ enum {
+     PROP_0 = 0,
+     PROP_BUS,
+@@ -146,6 +149,7 @@ struct _IBusWaylandIMPrivate
+     guint preedit_cursor_pos;
+     guint preedit_mode;
+     IBusModifierType modifiers;
++    gboolean is_virtual_latch_state;
+     gboolean hiding_preedit_text;
+     IBusInputHints ibus_hints;
+     IBusInputPurpose ibus_purpose;
+@@ -203,10 +207,6 @@ static char _use_sync_mode = 1;
+ 
+ static guint wayland_im_signals[LAST_SIGNAL] = { 0 };
+ 
+-static void         input_method_deactivate
+-                              (void                               *data,
+-                               struct zwp_input_method_union      *input_method,
+-                               struct zwp_input_method_context_v1 *context);
+ static GObject     *ibus_wayland_im_constructor        (GType          type,
+                                                         guint          n_params,
+                                                         GObjectConstructParam
+@@ -228,6 +228,19 @@ static gboolean     ibus_wayland_im_post_key           (IBusWaylandIM *wlim,
+                                                         xkb_keysym_t   sym,
+                                                         gboolean
+                                                                       filtered);
++static void         input_method_deactivate
++                              (void                               *data,
++                               struct zwp_input_method_union      *input_method,
++                               struct zwp_input_method_context_v1 *context);
++static void         input_method_keyboard_modifiers
++                              (void                               *data,
++                               struct zwp_keyboard_union          *keyboard,
++                               uint32_t                            key_serial,
++                               uint32_t
++                                                                 mods_depressed,
++                               uint32_t                            mods_latched,
++                               uint32_t                            mods_locked,
++                               uint32_t                            group);
+ 
+ 
+ static char
+@@ -1079,7 +1092,7 @@ ibus_xkb_keymap_update_with_keymap (IBusXkbKeymap     *ibus_keymap,
+         xkb_state_unref (ibus_keymap->state);
+     if (ibus_keymap->keymap)
+         xkb_keymap_unref (ibus_keymap->keymap);
+-    ibus_keymap->keymap = keymap;
++    ibus_keymap->keymap = xkb_keymap_ref (keymap);
+     ibus_keymap->state = state;
+ 
+     /* xkb_map_mod_get_index() can return any xkb_mod_index_t value, including
+@@ -1115,6 +1128,7 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+     IBusWaylandIMPrivate *priv;
+     IBusEngineDesc *desc;
+     struct xkb_keymap *keymap;
++    gboolean has_keymap = FALSE;
+ 
+     g_return_if_fail (IBUS_IS_BUS (bus));
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+@@ -1127,10 +1141,18 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+      * immediately without checking the Gsettings.
+      */
+     keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+-    if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
+-                                                       keymap)) {
++    if (keymap) {
++        has_keymap = ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                                         keymap);
+         xkb_keymap_unref (keymap);
+     }
++    if (priv->verbose) {
++        fprintf (priv->log, "New engine:%s keymap:%s state:%s\n",
++                 ibus_engine_desc_get_name (desc),
++                 has_keymap ? "TRUE" : "FALSE",
++                 priv->key_user.state ? "TRUE" : "FALSE");
++        fflush (priv->log);
++    }
+     g_object_unref (desc);
+ }
+ 
+@@ -1145,6 +1167,7 @@ input_method_keyboard_keymap (void                      *data,
+     IBusWaylandIM *wlim = data;
+     IBusWaylandIMPrivate *priv;
+     struct xkb_keymap *keymap;
++    gboolean has_keymap = FALSE;
+ 
+     if (!IBUS_IS_WAYLAND_IM (wlim)) {
+         close (fd);
+@@ -1194,25 +1217,25 @@ input_method_keyboard_keymap (void                      *data,
+         return;
+     }
+     keymap = create_system_xkb_keymap (priv->xkb_context, format, fd, size);
+-    if (keymap &&
+-        !ibus_xkb_keymap_update_with_keymap (&priv->key_sys,
+-                                             xkb_keymap_ref (keymap))) {
+-        xkb_keymap_unref (keymap);
+-        g_clear_pointer (&keymap, xkb_keymap_unref);
+-    }
+-    if (keymap && !priv->key_user.state &&
+-        !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
+-                                             xkb_keymap_ref (keymap))) {
+-        xkb_keymap_unref (keymap);
+-        g_clear_pointer (&keymap, xkb_keymap_unref);
+-    }
+-    if (keymap)
++    if (keymap) {
++        has_keymap = ibus_xkb_keymap_update_with_keymap (&priv->key_sys,
++                                                         keymap);
++        if (!has_keymap)
++            g_clear_pointer (&keymap, xkb_keymap_unref);
++    }
++    if (has_keymap && !priv->key_user.state) {
++        has_keymap = ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                                         keymap);
++        if (!has_keymap)
++            g_clear_pointer (&keymap, xkb_keymap_unref);
++    }
++    if (has_keymap)
+         xkb_keymap_unref (keymap);
+     if (priv->verbose) {
+         fprintf (priv->log, "System keymap format:%u fd:%d size:%u "
+                             "keymap:%s state:%s\n",
+                  format, fd, size,
+-                 keymap ? "TRUE" : "FALSE",
++                 has_keymap ? "TRUE" : "FALSE",
+                  priv->key_sys.state ? "TRUE" : "FALSE");
+         fflush (priv->log);
+     }
+@@ -1253,6 +1276,7 @@ _get_seat_with_name (GPtrArray *seats,
+     return NULL;
+ }
+ 
++
+ static gboolean
+ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                           uint32_t       key,
+@@ -1263,8 +1287,13 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+ {
+     IBusWaylandIMPrivate *priv;
+     IBusXkbKeymap *active_key;
++    xkb_mod_mask_t mods_depressed = 0, new_mods_depressed = 0;
++    xkb_mod_mask_t mods_locked;
++    xkb_layout_index_t  group = 0;
++    xkb_keysym_t system_sym = sym;
+     uint32_t code = key + 8;
+     uint32_t ch;
++    gboolean clear_virtual_state = FALSE;
+ 
+     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
+     priv = ibus_wayland_im_get_instance_private (wlim);
+@@ -1288,49 +1317,135 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+         active_key = &priv->key_user;
+     if (!active_key->state)
+         return FALSE;
++    mods_depressed = xkb_state_serialize_mods (active_key->state,
++                                               XKB_STATE_DEPRESSED |
++                                               XKB_STATE_LATCHED);
++    new_mods_depressed = mods_depressed;
++    mods_locked = xkb_state_serialize_mods (active_key->state,
++                                            XKB_STATE_MODS_LOCKED);
++    /* XKB group layout is configured in the system keymap but not the
++     * user keymap which always includes a single layout.
++     */
++    if (priv->key_sys.state) {
++        group = xkb_state_serialize_layout (priv->key_sys.state,
++                                            XKB_STATE_LAYOUT_LOCKED);
++    }
+     if (!filtered) {
+-#if 0
+-        /* FIXME: Need to confirm any modifiers should be ignored. */
+-        if (!ibus_accelerator_valid (
+-                    sym,
+-                    modifiers & IBUS_MODIFIER_MASK & ~IBUS_SHIFT_MASK)) {
+-            filtered = TRUE;
+-        }
+-#else
++        if (priv->key_sys.state)
++            system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+         switch (sym) {
+-        case IBUS_ISO_Level2_Latch:
+-        case IBUS_ISO_Level3_Latch:
+-        case IBUS_ISO_Level5_Latch:
++        case IBUS_KEY_ISO_Level2_Latch:
+             filtered = TRUE;
+             break;
++        /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
++         * Level3_Shift is caused by Alt key in lv(tilde) keymap.
++         */
++        case IBUS_KEY_ISO_Level3_Latch:
++        case IBUS_KEY_ISO_Level3_Shift:
++            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
++                new_mods_depressed |= active_key->mod5_mask;
++                if (sym != system_sym)
++                    priv->is_virtual_latch_state = TRUE;
++                filtered = TRUE;
++            }
++            break;
++        /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
++         */
++        case IBUS_KEY_ISO_Level5_Latch:
++        case IBUS_KEY_ISO_Level5_Shift:
++            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
++                new_mods_depressed |= active_key->mod3_mask;
++                if (sym != system_sym)
++                    priv->is_virtual_latch_state = TRUE;
++                filtered = TRUE;
++            }
++            break;
++        case IBUS_KEY_Shift_L:
++        case IBUS_KEY_Shift_R:
++            if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
++                new_mods_depressed |= active_key->shift_mask;
++            else
++                new_mods_depressed &= ~active_key->shift_mask;
++            break;
+         default:;
+         }
++#ifdef IBUS_LOG_SHOW_KEYSYM
++        if (priv->verbose) {
++            fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
++                                "state:%s filtered:%d\n",
++                     G_STRFUNC,
++                     key, sym, system_sym, modifiers,
++                     state ? "press" : "release",
++                     filtered);
++            fflush (priv->log);
++        }
+ #endif
+     }
+-    /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
+-     * but forwards the key events to the focused application here.
+-     * Because some applications treat the printable keys as control keys,
+-     * E.g. game apps "hjkl" use the cursor move like VI mode.
+-     * Unfortunately the Wayland input-method protocol does not provide
+-     * the fallback logic after apps handle the key events like GTK3/2
+-     * IM modules. But The input-method always should handles key events
+-     * prior to apps.
+-     */
+-    if (!filtered && (state != WL_KEYBOARD_KEY_STATE_RELEASED) &&
+-        !priv->use_sys_keymap) {
+-        ch = xkb_state_key_get_utf32 (active_key->state, code);
+-        if (!(modifiers & IBUS_MODIFIER_MASK & ~IBUS_SHIFT_MASK) &&
+-            ch && ch != '\n' && ch != '\b' && ch != '\r' && ch != '\t' &&
+-            ch != '\033' && ch != '\x7f') {
+-            gchar buff[8] = { 0, };
+-            buff[g_unichar_to_utf8 (ch, buff)] = '\0';
+-            ibus_wayland_im_commit_text (wlim, buff);
+-            filtered = TRUE;
++    if (state != WL_KEYBOARD_KEY_STATE_RELEASED) {
++        ch = ibus_keyval_to_unicode (sym);
++        if (ch == 0 || g_unichar_iscntrl (ch))
++            ch = xkb_state_key_get_utf32 (active_key->state, code);
++/* No text with Control & Alt & Super keys */
++#ifndef GDK_WINDOWING_QUARTZ
++#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
++    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
++#else
++#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
++    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
++#endif
++        if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
++             !g_unichar_iscntrl (ch)) || filtered) {
++        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
++         * but forwards the key events to the focused application here.
++         * Because some applications treat the printable keys as control keys,
++         * E.g. game apps "hjkl" use the cursor move like VI mode.
++         * Unfortunately the Wayland input-method protocol does not provide
++         * the fallback logic after apps handle the key events like GTK3/2
++         * IM modules. But the input-method always should handle key events
++         * prior to apps.
++         */
++            if (!filtered && !priv->use_sys_keymap) {
++                gchar buff[8] = { 0, };
++                g_unichar_to_utf8 (ch, buff);
++                ibus_wayland_im_commit_text (wlim, buff);
++                filtered = TRUE;
++            }
++            /* If `filtered` is %TRUE, the keysym can be eaten for the compose
++             * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
++             * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)"
++             * keymap. However if you configure multiple keymaps, AltGr-Shift
++             * may produce the keymap switch although AltGr-Shift-V produces
++             * `Greek_OMEGA`.
++             * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
++             */
++            if (modifiers & IBUS_MOD3_MASK) {
++                new_mods_depressed &= ~active_key->mod3_mask;
++                clear_virtual_state = TRUE;
++            }
++            if (modifiers & IBUS_MOD5_MASK) {
++                new_mods_depressed &= ~active_key->mod5_mask;
++                clear_virtual_state = TRUE;
++            }
+         }
++#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
++    }
++    if (priv->is_virtual_latch_state) {
++        if (new_mods_depressed != mods_depressed) {
++            input_method_keyboard_modifiers (wlim, NULL, 0,
++                                             new_mods_depressed,
++                                             0,
++                                             mods_locked,
++                                             group);
++        }
++        if (clear_virtual_state)
++            priv->is_virtual_latch_state = FALSE;
++    }
++    if (!priv->is_virtual_latch_state ||
++        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
++        xkb_state_update_key (active_key->state, code,
++                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
++                              ? XKB_KEY_UP : XKB_KEY_DOWN);
+     }
+-    xkb_state_update_key (active_key->state, code,
+-                          (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+-                          ? XKB_KEY_UP : XKB_KEY_DOWN);
+     return filtered;
+ }
+ 
+@@ -1777,12 +1892,36 @@ input_method_keyboard_key (void                      *data,
+     /* xkb_key_get_syms() does not return the capital syms with Shift key. */
+     if (priv->key_sys.state)
+         event.sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+-    if (event.sym != IBUS_KEY_Multi_key && priv->key_user.state)
+-        event.sym = xkb_state_key_get_one_sym (priv->key_user.state, code);
++    switch (event.sym) {
++    case IBUS_KEY_Multi_key:
++    case IBUS_KEY_ISO_Next_Group:
++        break;
++    default:
++        if (priv->key_user.state)
++            event.sym = xkb_state_key_get_one_sym (priv->key_user.state, code);
++    }
+     event.modifiers = priv->modifiers;
+     if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+         event.modifiers |= IBUS_RELEASE_MASK;
+     event.wlim = wlim;
++#ifdef IBUS_LOG_SHOW_KEYSYM
++    if (priv->verbose) {
++        fprintf (priv->log, "%s serial:%u time:%u key:%u "
++                            "sym:%x system_user:%x system_sym:%x "
++                            "modifiers:%x state:%s\n",
++                 G_STRFUNC,
++                 key_serial, time, key,
++                 event.sym,
++                 priv->key_user.state ?
++                     xkb_state_key_get_one_sym (priv->key_user.state, code) :
++                     0,
++                 priv->key_sys.state ?
++                     xkb_state_key_get_one_sym (priv->key_sys.state, code) :
++                     0,
++                 event.modifiers, state ? "press" : "release");
++        fflush (priv->log);
++    }
++#endif
+ 
+     key_event_check_repeat (wlim, &event);
+     switch (_use_sync_mode) {
+@@ -1796,6 +1935,29 @@ input_method_keyboard_key (void                      *data,
+ }
+ 
+ 
++/**
++ * input_method_keyboard_modifiers:
++ *
++ * This is a common API of input_method_keyboard_modifiers_v1() and
++ * input_method_keyboard_modifiers_v2().
++ * If you configure multiple XKB layouts in the system with
++ * /etc/vconsole.conf file, you can switch the active layout with
++ * the XKB options and @group is changed with the shortcut key like
++ * "grp:lalt_lshift_toggle" but If you click the keyboard icon in
++ * Plasma KDE and change the active layout with GUI, @group is not
++ * changed and I assume it's a bug [1].
++ * Currently there are two cases of the combinations of the IBus keymap and
++ * the system keymap supported by IBus:
++ * 1. Always Same keymaps
++ *    E.g. IBus keymap is "lv(tilde)" and system one is "lv(tilde)"
++ * 2. System keymap is "US" and switch IBus keymaps with Super-space key
++ *    E.g. IBus keymap is "lv(tilde)" and system one is "us"
++ * So you should use both XKB option keys and IBus shortcutkeys to switch the
++ * keymaps to change both XKB keymaps and IBus keymaps but you should not
++ * click the keyboard icon in KDE.
++ *
++ * [1] https://bugs.kde.org/show_bug.cgi?id=518371
++ */
+ static void
+ input_method_keyboard_modifiers (void                      *data,
+                                  struct zwp_keyboard_union *keyboard,
+@@ -1816,27 +1978,59 @@ input_method_keyboard_modifiers (void                      *data,
+         active_key = &priv->key_sys;
+     else
+         active_key = &priv->key_user;
+-    if (priv->key_user.state) {
+-        xkb_state_update_mask (priv->key_user.state, mods_depressed,
+-                               mods_latched, mods_locked, 0, 0, group);
+-    }
+-    /* Pressing XKB group key likes Alt_R with grp:toggle calls
+-     * input_method_keyboard_modifiers() with the updated `group`
+-     * and need to update priv->key_sys.state to switch the XKB group
+-     * in the system keymap.
+-     *
+-     * XKB group key can switch `group` but clicking the keyboard icon
+-     * of KDE does not change `group` at present. Maybe a bug in the
+-     * KDE Wayland compositor.
++    /* Do not reset Latch modifiers by system in case the system keymap
++     * has no the Latch key.
++     * In case the user keymap is "lv(tilde)" and the system one is "us",
++     * input_method_keyboard_modifiers() clears state of Level3_Latch key
++     * immediately after the Level3_Latch key is released because the "us"
++     * keymap does not have the latch keys.
++     * The behavior is different in case both user and system keymaps are
++     * "lv(tilde)".
++     * So `is_virtual_latch_state` keeps the virtual state of the latch keys
++     * not to override the state of the "us" keymap.
+      */
+-    if (priv->key_sys.state) {
+-        xkb_state_update_mask (priv->key_sys.state, mods_depressed,
+-                               mods_latched, mods_locked, 0, 0, group);
++    if (!priv->is_virtual_latch_state || key_serial == 0) {
++        if (priv->key_user.state) {
++            xkb_state_update_mask (priv->key_user.state, mods_depressed,
++                                   mods_latched, mods_locked, 0, 0, group);
++        }
++        /* Pressing XKB group key likes Alt_R with grp:toggle calls
++         * input_method_keyboard_modifiers() with the updated `group`
++         * and need to update priv->key_sys.state to switch the XKB group
++         * in the system keymap.
++         *
++         * XKB group key can switch `group` but clicking the keyboard icon
++         * of KDE does not change `group` at present. Maybe a bug in the
++         * KDE Wayland compositor.
++         */
++        if (priv->key_sys.state) {
++            xkb_state_update_mask (priv->key_sys.state, mods_depressed,
++                                   mods_latched, mods_locked, 0, 0, group);
++        }
+     }
+     if (active_key->state) {
++        /* CapsLock needs XKB_STATE_MODS_LOCKED */
+         mask = xkb_state_serialize_mods (active_key->state,
+                                          XKB_STATE_DEPRESSED |
+-                                         XKB_STATE_LATCHED);
++                                         XKB_STATE_LATCHED |
++                                         XKB_STATE_MODS_LOCKED);
++    }
++    if (priv->verbose) {
++        struct xkb_state *state = active_key->state;
++        fprintf (priv->log, "Update modifiers serial:%u depress:%X latch:%X "
++                             "lock:%X group:%X orig_depre:%X orig_latch:%X "
++                             "orig_lock:%X\n",
++                 key_serial, mods_depressed, mods_latched, mods_locked, group,
++                 state ?
++                     xkb_state_serialize_mods (state, XKB_STATE_DEPRESSED) :
++                     0xFFFFFFFF,
++                 state ?
++                     xkb_state_serialize_mods (state, XKB_STATE_LATCHED) :
++                     0xFFFFFFFF,
++                 state ?
++                     xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED) :
++                     0xFFFFFFFF);
++        fflush (priv->log);
+     }
+ 
+     priv->modifiers = 0;
+@@ -1863,6 +2057,9 @@ input_method_keyboard_modifiers (void                      *data,
+     if (mask & active_key->meta_mask)
+         priv->modifiers |= IBUS_META_MASK;
+ 
++    if (!key_serial)
++        return;
++
+     switch (priv->version) {
+     case INPUT_METHOD_V1:
+         zwp_input_method_context_v1_modifiers (priv->context, key_serial,
+@@ -2834,6 +3031,7 @@ ibus_wayland_im_constructor (GType                  type,
+     IBusWaylandSeat *seat = NULL;
+     IBusEngineDesc *desc;
+     struct xkb_keymap *keymap = NULL;
++    gboolean has_keymap = FALSE;
+ 
+     object = G_OBJECT_CLASS (ibus_wayland_im_parent_class)->constructor (
+             type, n_params, params);
+@@ -2882,9 +3080,21 @@ ibus_wayland_im_constructor (GType                  type,
+     desc = ibus_bus_get_global_engine (priv->ibusbus);
+     if (desc)
+         keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+-    if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
+-                                                       keymap)) {
+-        g_clear_pointer (&keymap, xkb_keymap_unref);
++    if (keymap) {
++        has_keymap = ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                                         keymap);
++        xkb_keymap_unref (keymap);
++    }
++    if (priv->verbose) {
++        if (!desc) {
++            fprintf (priv->log, "Constructor has no global engine\n");
++        } else {
++            fprintf (priv->log, "Constructor engine %s keymap:%s state:%s\n",
++                     ibus_engine_desc_get_name (desc),
++                     has_keymap ? "TRUE" : "FALSE",
++                     priv->key_user.state ? "TRUE" : "FALSE");
++        }
++        fflush (priv->log);
+     }
+     ibus_bus_set_watch_ibus_signal (priv->ibusbus, TRUE);
+     g_signal_connect (priv->ibusbus, "global-engine-changed",
+-- 
+2.54.0
+
+From df8230ff90ec3aafffe10af769ad66aa948ac349 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 29 Jun 2026 12:51:07 +0900
+Subject: [PATCH 2/5] client/wayland: Refactor ibus_wayland_im_post_key()
+
+Separate 3 functions.
+
+Fixes: #2869
+Closes: rhbz#2444009
+---
+ client/wayland/ibuswaylandim.c | 377 +++++++++++++++++++++------------
+ 1 file changed, 246 insertions(+), 131 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index 95b330f8..b16ff521 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -367,6 +367,81 @@ ibus_wayland_im_commit_text (IBusWaylandIM *wlim,
+ }
+ 
+ 
++static gboolean
++ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
++                                  uint32_t        key,
++                                  uint32_t        modifiers,
++                                  uint32_t        state,
++                                  xkb_keysym_t    sym,
++                                  IBusXkbKeymap  *active_key,
++                                  gboolean        filtered,
++                                  xkb_mod_mask_t *new_mods_depressed,
++                                  gboolean       *clear_virtual_state)
++{
++    IBusWaylandIMPrivate *priv;
++    uint32_t code = key + 8;
++    uint32_t ch;
++
++    g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), filtered);
++    priv = ibus_wayland_im_get_instance_private (wlim);
++
++    if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
++        return filtered;
++
++    ch = ibus_keyval_to_unicode (sym);
++    if (ch == 0 || g_unichar_iscntrl (ch))
++        ch = xkb_state_key_get_utf32 (active_key->state, code);
++
++/* No text with Control & Alt & Super keys */
++#ifndef GDK_WINDOWING_QUARTZ
++#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
++    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
++#else
++#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
++    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
++#endif
++    if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
++         !g_unichar_iscntrl (ch)) || filtered) {
++        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
++         * but forwards the key events to the focused application here.
++         * Because some applications treat the printable keys as control keys,
++         * E.g. game apps "hjkl" use the cursor move like VI mode.
++         * Unfortunately the Wayland input-method protocol does not provide
++         * the fallback logic after apps handle the key events like GTK3/2
++         * IM modules. But the input-method always should handle key events
++         * prior to apps.
++         */
++        if (!filtered && !priv->use_sys_keymap) {
++            gchar buff[8] = { 0, };
++            g_unichar_to_utf8 (ch, buff);
++            ibus_wayland_im_commit_text (wlim, buff);
++            filtered = TRUE;
++        }
++        /* If `filtered` is %TRUE, the keysym can be eaten for the compose
++         * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
++         * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)" keymap.
++         * However if you configure multiple keymaps, AltGr-Shift may produce
++         * the keymap switch although AltGr-Shift-V produces `Greek_OMEGA`.
++         * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
++         */
++        if (modifiers & IBUS_MOD3_MASK) {
++            if (new_mods_depressed)
++                *new_mods_depressed &= ~active_key->mod3_mask;
++            if (clear_virtual_state)
++                *clear_virtual_state = TRUE;
++        }
++        if (modifiers & IBUS_MOD5_MASK) {
++            if (new_mods_depressed)
++                *new_mods_depressed &= ~active_key->mod5_mask;
++            if (clear_virtual_state)
++                *clear_virtual_state = TRUE;
++        }
++    }
++#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
++    return filtered;
++}
++
++
+ static void
+ ibus_wayland_im_key (IBusWaylandIM *wlim,
+                      uint32_t       key_serial,
+@@ -431,6 +506,152 @@ ibus_wayland_im_keysym (IBusWaylandIM *wlim,
+ }
+ 
+ 
++/**
++ * ibus_wayland_im_update_virtual_depressed:
++ *
++ * If the IBus keymap is different from the compositor keymap,
++ * IBus needs to maintain the modifiers state by itself to call
++ * xkb_state_update_mask(), E.g. IBus keymap is "lv(tilde)" and the system
++ * one is "us", because input_method_keyboard_modifiers()
++ * is not called by the Wayland compositor in that case.
++ * So this API updates @mods_depressed with ISO level3 and level5
++ * latch and shift states, and send it to
++ * ibus_wayland_im_update_virtual_xkb_state().
++ */
++static gboolean
++ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
++                                          uint32_t        key,
++                                          uint32_t        modifiers,
++                                          uint32_t        state,
++                                          xkb_keysym_t    sym,
++                                          IBusXkbKeymap  *active_key,
++                                          gboolean        filtered,
++                                          xkb_mod_mask_t *mods_depressed)
++{
++    IBusWaylandIMPrivate *priv;
++    uint32_t code = key + 8;
++    xkb_mod_mask_t new_mods_depressed;
++    xkb_keysym_t system_sym = sym;
++
++    if (filtered)
++        return filtered;
++
++    g_assert (IBUS_IS_WAYLAND_IM (wlim));
++    g_assert (active_key);
++    g_assert (mods_depressed);
++
++    priv = ibus_wayland_im_get_instance_private (wlim);
++    new_mods_depressed = *mods_depressed;
++
++    if ((modifiers & ~IBUS_RELEASE_MASK ) != new_mods_depressed) {
++        g_warning ("IBus modifiers %X is different from XKB depressed %X",
++                   modifiers, new_mods_depressed);
++    }
++    if (priv->key_sys.state)
++        system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
++    switch (sym) {
++    case IBUS_KEY_ISO_Level2_Latch:
++        filtered = TRUE;
++        break;
++    /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
++     * Level3_Shift is caused by Alt key in lv(tilde) keymap.
++     */
++    case IBUS_KEY_ISO_Level3_Latch:
++    case IBUS_KEY_ISO_Level3_Shift:
++        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
++            new_mods_depressed |= active_key->mod5_mask;
++            if (sym != system_sym)
++                priv->is_virtual_latch_state = TRUE;
++            filtered = TRUE;
++        }
++        break;
++    /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
++     */
++    case IBUS_KEY_ISO_Level5_Latch:
++    case IBUS_KEY_ISO_Level5_Shift:
++        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
++            new_mods_depressed |= active_key->mod3_mask;
++            if (sym != system_sym)
++                priv->is_virtual_latch_state = TRUE;
++            filtered = TRUE;
++        }
++        break;
++    case IBUS_KEY_Shift_L:
++    case IBUS_KEY_Shift_R:
++        if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
++            new_mods_depressed |= active_key->shift_mask;
++        else
++            new_mods_depressed &= ~active_key->shift_mask;
++        break;
++    default:;
++    }
++#ifdef IBUS_LOG_SHOW_KEYSYM
++    if (priv->verbose) {
++        fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
++                            "state:%s filtered:%d\n",
++                 G_STRFUNC,
++                 key, sym, system_sym, modifiers, state ? "press" : "release",
++                 filtered);
++        fflush (priv->log);
++    }
++#endif
++    *mods_depressed = new_mods_depressed;
++    return filtered;
++}
++
++
++/**
++ * ibus_wayland_im_update_virtual_xkb_state:
++ *
++ * Update virtual IBus xkb_state by KeyPress and KeyRelease.
++ */
++static void
++ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
++                                          uint32_t       key,
++                                          uint32_t       state,
++                                          IBusXkbKeymap *active_key,
++                                          xkb_mod_mask_t mods_depressed,
++                                          xkb_mod_mask_t new_mods_depressed,
++                                          gboolean       clear_virtual_state)
++{
++    IBusWaylandIMPrivate *priv;
++    uint32_t code = key + 8;
++    xkb_mod_mask_t mods_locked;
++    xkb_layout_index_t  group = 0;
++
++    g_assert (IBUS_IS_WAYLAND_IM (wlim));
++
++    priv = ibus_wayland_im_get_instance_private (wlim);
++    mods_locked = xkb_state_serialize_mods (active_key->state,
++                                            XKB_STATE_MODS_LOCKED);
++    /* XKB group layout is configured in the system keymap but not the
++     * user keymap which always includes a single layout.
++     */
++    if (priv->key_sys.state) {
++        group = xkb_state_serialize_layout (priv->key_sys.state,
++                                            XKB_STATE_LAYOUT_LOCKED);
++    }
++
++    if (priv->is_virtual_latch_state) {
++        if (new_mods_depressed != mods_depressed) {
++            input_method_keyboard_modifiers (wlim, NULL, 0,
++                                             new_mods_depressed,
++                                             0,
++                                             mods_locked,
++                                             group);
++        }
++        if (clear_virtual_state)
++            priv->is_virtual_latch_state = FALSE;
++    }
++    if (!priv->is_virtual_latch_state ||
++        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
++        xkb_state_update_key (active_key->state, code,
++                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
++                              ? XKB_KEY_UP : XKB_KEY_DOWN);
++    }
++}
++
++
+ static void
+ _context_commit_text_cb (IBusInputContext *context,
+                          IBusText         *text,
+@@ -1287,12 +1508,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+ {
+     IBusWaylandIMPrivate *priv;
+     IBusXkbKeymap *active_key;
+-    xkb_mod_mask_t mods_depressed = 0, new_mods_depressed = 0;
+-    xkb_mod_mask_t mods_locked;
+-    xkb_layout_index_t  group = 0;
+-    xkb_keysym_t system_sym = sym;
+-    uint32_t code = key + 8;
+-    uint32_t ch;
++    xkb_mod_mask_t mods_depressed, new_mods_depressed;
+     gboolean clear_virtual_state = FALSE;
+ 
+     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
+@@ -1321,131 +1537,30 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                XKB_STATE_DEPRESSED |
+                                                XKB_STATE_LATCHED);
+     new_mods_depressed = mods_depressed;
+-    mods_locked = xkb_state_serialize_mods (active_key->state,
+-                                            XKB_STATE_MODS_LOCKED);
+-    /* XKB group layout is configured in the system keymap but not the
+-     * user keymap which always includes a single layout.
+-     */
+-    if (priv->key_sys.state) {
+-        group = xkb_state_serialize_layout (priv->key_sys.state,
+-                                            XKB_STATE_LAYOUT_LOCKED);
+-    }
+-    if (!filtered) {
+-        if (priv->key_sys.state)
+-            system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+-        switch (sym) {
+-        case IBUS_KEY_ISO_Level2_Latch:
+-            filtered = TRUE;
+-            break;
+-        /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
+-         * Level3_Shift is caused by Alt key in lv(tilde) keymap.
+-         */
+-        case IBUS_KEY_ISO_Level3_Latch:
+-        case IBUS_KEY_ISO_Level3_Shift:
+-            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-                new_mods_depressed |= active_key->mod5_mask;
+-                if (sym != system_sym)
+-                    priv->is_virtual_latch_state = TRUE;
+-                filtered = TRUE;
+-            }
+-            break;
+-        /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
+-         */
+-        case IBUS_KEY_ISO_Level5_Latch:
+-        case IBUS_KEY_ISO_Level5_Shift:
+-            if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-                new_mods_depressed |= active_key->mod3_mask;
+-                if (sym != system_sym)
+-                    priv->is_virtual_latch_state = TRUE;
+-                filtered = TRUE;
+-            }
+-            break;
+-        case IBUS_KEY_Shift_L:
+-        case IBUS_KEY_Shift_R:
+-            if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+-                new_mods_depressed |= active_key->shift_mask;
+-            else
+-                new_mods_depressed &= ~active_key->shift_mask;
+-            break;
+-        default:;
+-        }
+-#ifdef IBUS_LOG_SHOW_KEYSYM
+-        if (priv->verbose) {
+-            fprintf (priv->log, "%s key:%u sym:%x system_sym:%x modifiers:%x "
+-                                "state:%s filtered:%d\n",
+-                     G_STRFUNC,
+-                     key, sym, system_sym, modifiers,
+-                     state ? "press" : "release",
+-                     filtered);
+-            fflush (priv->log);
+-        }
+-#endif
+-    }
+-    if (state != WL_KEYBOARD_KEY_STATE_RELEASED) {
+-        ch = ibus_keyval_to_unicode (sym);
+-        if (ch == 0 || g_unichar_iscntrl (ch))
+-            ch = xkb_state_key_get_utf32 (active_key->state, code);
+-/* No text with Control & Alt & Super keys */
+-#ifndef GDK_WINDOWING_QUARTZ
+-#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
+-    IBUS_CONTROL_MASK | IBUS_MOD1_MASK | IBUS_MOD4_MASK)
+-#else
+-#  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
+-    IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
+-#endif
+-        if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
+-             !g_unichar_iscntrl (ch)) || filtered) {
+-        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
+-         * but forwards the key events to the focused application here.
+-         * Because some applications treat the printable keys as control keys,
+-         * E.g. game apps "hjkl" use the cursor move like VI mode.
+-         * Unfortunately the Wayland input-method protocol does not provide
+-         * the fallback logic after apps handle the key events like GTK3/2
+-         * IM modules. But the input-method always should handle key events
+-         * prior to apps.
+-         */
+-            if (!filtered && !priv->use_sys_keymap) {
+-                gchar buff[8] = { 0, };
+-                g_unichar_to_utf8 (ch, buff);
+-                ibus_wayland_im_commit_text (wlim, buff);
+-                filtered = TRUE;
+-            }
+-            /* If `filtered` is %TRUE, the keysym can be eaten for the compose
+-             * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
+-             * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)"
+-             * keymap. However if you configure multiple keymaps, AltGr-Shift
+-             * may produce the keymap switch although AltGr-Shift-V produces
+-             * `Greek_OMEGA`.
+-             * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
+-             */
+-            if (modifiers & IBUS_MOD3_MASK) {
+-                new_mods_depressed &= ~active_key->mod3_mask;
+-                clear_virtual_state = TRUE;
+-            }
+-            if (modifiers & IBUS_MOD5_MASK) {
+-                new_mods_depressed &= ~active_key->mod5_mask;
+-                clear_virtual_state = TRUE;
+-            }
+-        }
+-#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
+-    }
+-    if (priv->is_virtual_latch_state) {
+-        if (new_mods_depressed != mods_depressed) {
+-            input_method_keyboard_modifiers (wlim, NULL, 0,
+-                                             new_mods_depressed,
+-                                             0,
+-                                             mods_locked,
+-                                             group);
+-        }
+-        if (clear_virtual_state)
+-            priv->is_virtual_latch_state = FALSE;
+-    }
+-    if (!priv->is_virtual_latch_state ||
+-        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
+-        xkb_state_update_key (active_key->state, code,
+-                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+-                              ? XKB_KEY_UP : XKB_KEY_DOWN);
+-    }
++    filtered = ibus_wayland_im_update_virtual_depressed (wlim,
++                                                         key,
++                                                         modifiers,
++                                                         state,
++                                                         sym,
++                                                         active_key,
++                                                         filtered,
++                                                         &new_mods_depressed);
++    filtered = ibus_wayland_im_commit_key_event (wlim,
++                                                 key,
++                                                 modifiers,
++                                                 state,
++                                                 sym,
++                                                 active_key,
++                                                 filtered,
++                                                 &new_mods_depressed,
++                                                 &clear_virtual_state);
++    ibus_wayland_im_update_virtual_xkb_state (wlim,
++                                              key,
++                                              state,
++                                              active_key,
++                                              mods_depressed,
++                                              new_mods_depressed,
++                                              clear_virtual_state);
+     return filtered;
+ }
+ 
+-- 
+2.54.0
+
+From 056b3e59cc91bdd97efc8b28860bf21e36416afd Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Wed, 17 Jun 2026 19:51:08 +0900
+Subject: [PATCH 3/5] client/wayland: Refactor ISO_Level[3|5]_Shift state
+
+The states of ISO Shift keys should be cleared when the key is released
+against ISO Latch keys. E.g. "RALT" key in "lv(tilde)" keymap.
+
+The state of the "RALT" key is strange in "de(T3)" keymap, i.e.
+"ISO_Level3_Shift" keysym for the keypress and ISO_Level5_Latch keysm
+for keyrelease and IBus could not get the correct dead keys and
+clear the state. Now the "is_pressed_mod5" flag checks if the
+key is pressed and add a workaround.
+
+Fixes: #2869
+Closes: rhbz#2444009
+---
+ client/wayland/ibuswaylandim.c | 139 ++++++++++++++++++++++++++++++---
+ 1 file changed, 127 insertions(+), 12 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index b16ff521..97e3ce38 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -64,6 +64,13 @@ typedef enum
+     INPUT_METHOD_V2,
+ } IMProtocolVersion;
+ 
++typedef enum {
++    IBUS_KEY_ISO_LEVEL_INVALID,
++    IBUS_KEY_ISO_LEVEL_2,
++    IBUS_KEY_ISO_LEVEL_3,
++    IBUS_KEY_ISO_LEVEL_5
++} IBusKeyIsoLevelValue;
++
+ struct zwp_input_method_context_union {
+     union {
+         struct zwp_input_method_context_v1 *context_v1;
+@@ -163,6 +170,8 @@ struct _IBusWaylandIMPrivate
+ 
+     IBusXkbKeymap key_user;
+     IBusXkbKeymap key_sys;
++    gboolean is_pressed_mod3;
++    gboolean is_pressed_mod5;
+ 
+     uint32_t im_serial;
+     int32_t repeat_rate;
+@@ -341,6 +350,28 @@ ibus_wayland_source_new (struct wl_display *display)
+ }
+ 
+ 
++static void
++ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
++{
++    IBusWaylandIMPrivate *priv;
++    xkb_layout_index_t  group = 0;
++
++    g_assert (IBUS_IS_WAYLAND_IM (wlim));
++
++    priv = ibus_wayland_im_get_instance_private (wlim);
++    priv->is_virtual_latch_state = FALSE;
++    priv->is_pressed_mod3 = FALSE;
++    priv->is_pressed_mod5 = FALSE;
++    if (priv->key_user.state && priv->key_sys.state) {
++        group = xkb_state_serialize_layout (priv->key_sys.state,
++                                            XKB_STATE_LAYOUT_LOCKED);
++        input_method_keyboard_modifiers (wlim, NULL, 0, 0, 0, 0, group);
++    } else {
++        priv->modifiers = 0;
++    }
++}
++
++
+ static void
+ ibus_wayland_im_commit_text (IBusWaylandIM *wlim,
+                              const char    *str)
+@@ -526,7 +557,8 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+                                           xkb_keysym_t    sym,
+                                           IBusXkbKeymap  *active_key,
+                                           gboolean        filtered,
+-                                          xkb_mod_mask_t *mods_depressed)
++                                          xkb_mod_mask_t *mods_depressed,
++                                          gboolean       *clear_virtual_state)
+ {
+     IBusWaylandIMPrivate *priv;
+     uint32_t code = key + 8;
+@@ -539,6 +571,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     g_assert (IBUS_IS_WAYLAND_IM (wlim));
+     g_assert (active_key);
+     g_assert (mods_depressed);
++    g_assert (clear_virtual_state);
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     new_mods_depressed = *mods_depressed;
+@@ -555,25 +588,87 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+         break;
+     /* Level3_latch is caused by TLDE key in lv(tilde) keymap.
+      * Level3_Shift is caused by Alt key in lv(tilde) keymap.
++     * Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
+      */
+     case IBUS_KEY_ISO_Level3_Latch:
+     case IBUS_KEY_ISO_Level3_Shift:
+-        if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-            new_mods_depressed |= active_key->mod5_mask;
+-            if (sym != system_sym)
+-                priv->is_virtual_latch_state = TRUE;
+-            filtered = TRUE;
+-        }
+-        break;
+-    /* Level5_Latch is caused by Shift+Alt key in de(T3) keymap.
+-     */
+     case IBUS_KEY_ISO_Level5_Latch:
+     case IBUS_KEY_ISO_Level5_Shift:
+         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-            new_mods_depressed |= active_key->mod3_mask;
++            switch (sym) {
++            case IBUS_KEY_ISO_Level3_Latch:
++            case IBUS_KEY_ISO_Level3_Shift:
++                priv->is_pressed_mod5 = TRUE;
++                new_mods_depressed |= active_key->mod5_mask;
++                break;
++            case IBUS_KEY_ISO_Level5_Latch:
++            case IBUS_KEY_ISO_Level5_Shift:
++                priv->is_pressed_mod3 = TRUE;
++                new_mods_depressed |= active_key->mod3_mask;
++                break;
++            default:
++                g_assert_not_reached ();
++            }
+             if (sym != system_sym)
+                 priv->is_virtual_latch_state = TRUE;
+             filtered = TRUE;
++        } else {
++            IBusKeyIsoLevelValue current_level = IBUS_KEY_ISO_LEVEL_INVALID;
++            gboolean *is_pressed_current_mod = NULL;
++            gboolean *is_pressed_reverse_mod = NULL;
++            xkb_mod_mask_t current_mode_mask = 0;
++            xkb_mod_mask_t reverse_mode_mask = 0;
++            switch (sym) {
++            case IBUS_KEY_ISO_Level3_Latch:
++            case IBUS_KEY_ISO_Level3_Shift:
++                current_level = IBUS_KEY_ISO_LEVEL_3;
++                is_pressed_current_mod = &priv->is_pressed_mod5;
++                is_pressed_reverse_mod = &priv->is_pressed_mod3;
++                current_mode_mask = active_key->mod5_mask;
++                reverse_mode_mask = active_key->mod3_mask;
++                break;
++            case IBUS_KEY_ISO_Level5_Latch:
++            case IBUS_KEY_ISO_Level5_Shift:
++                current_level = IBUS_KEY_ISO_LEVEL_5;
++                is_pressed_current_mod = &priv->is_pressed_mod3;
++                is_pressed_reverse_mod = &priv->is_pressed_mod5;
++                current_mode_mask = active_key->mod3_mask;
++                reverse_mode_mask = active_key->mod5_mask;
++                break;
++            default:;
++            }
++            g_assert (current_level != IBUS_KEY_ISO_LEVEL_INVALID);
++            g_assert (is_pressed_current_mod && is_pressed_reverse_mod);
++            if (G_LIKELY (*is_pressed_current_mod)) {
++                *is_pressed_current_mod = FALSE;
++            } else if (*is_pressed_reverse_mod &&
++                       (new_mods_depressed & active_key->mod3_mask)) {
++                /* The unlikely case !priv->is_pressed_mod3 and
++                 * priv->is_pressed_mod5 means that "de(T3)" keymap gives
++                 * the pressed "Level3_Shift" keysym and
++                 * the released "Level5_Latch" keysym for the key <RALT>.
++                 * Maybe a bug in "de(T3)" so need to clear the MOD5 state
++                 * by the pressed "Level3_Shift" keysym.
++                 */
++                *is_pressed_reverse_mod = FALSE;
++                new_mods_depressed &= ~reverse_mode_mask;
++                *clear_virtual_state = TRUE;
++                g_debug ("Got a wrong released %s key without the "
++                         "pressed one. Maybe a bug in XKB.",
++                         current_level == IBUS_KEY_ISO_LEVEL_3 ?
++                                 "Level3" : "Level5");
++            }
++            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
++             * with the key <RALT>.
++             */
++            if ((current_level == IBUS_KEY_ISO_LEVEL_3 &&
++                 sym == IBUS_KEY_ISO_Level3_Shift) ||
++                (current_level == IBUS_KEY_ISO_LEVEL_5 &&
++                 sym == IBUS_KEY_ISO_Level5_Shift)) {
++                new_mods_depressed &= ~current_mode_mask;
++                if (sym != system_sym)
++                    clear_virtual_state = TRUE;
++            }
+         }
+         break;
+     case IBUS_KEY_Shift_L:
+@@ -649,6 +744,23 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+                               (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+                               ? XKB_KEY_UP : XKB_KEY_DOWN);
+     }
++    if (priv->is_virtual_latch_state &&
++        (new_mods_depressed != mods_depressed) &&
++        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
++        xkb_mod_mask_t new2_mods_depressed;
++        new2_mods_depressed = xkb_state_serialize_mods (active_key->state,
++                                                        XKB_STATE_DEPRESSED |
++                                                        XKB_STATE_LATCHED);
++        /* "de(T3)" keymap gives the pressed "Level3_Shift" keysym and
++         * sets both MOD3(Level5) and MOD5(Level3) states after
++         * xkb_state_update_key() is called with the keypress.
++         * Maybe a bug in "de(T3)" and sets MOD5(Level3) again here.
++         */
++        if (G_UNLIKELY (new_mods_depressed != new2_mods_depressed)) {
++            xkb_state_update_mask (active_key->state, new_mods_depressed,
++                                   0, mods_locked, 0, 0, group);
++        }
++    }
+ }
+ 
+ 
+@@ -1355,6 +1467,7 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     desc = ibus_bus_get_global_engine (bus);
++    ibus_wayland_im_reset_modifiers (wlim);
+     g_assert (desc);
+     g_assert (!g_strcmp0 (ibus_engine_desc_get_name (desc), engine_name));
+     /* Always update priv->key_user even if priv->use_sys_keymap is %FALSE
+@@ -1544,7 +1657,8 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                          sym,
+                                                          active_key,
+                                                          filtered,
+-                                                         &new_mods_depressed);
++                                                         &new_mods_depressed,
++                                                         &clear_virtual_state);
+     filtered = ibus_wayland_im_commit_key_event (wlim,
+                                                  key,
+                                                  modifiers,
+@@ -2427,6 +2541,7 @@ _create_input_context_done (GObject      *object,
+                                                            TRUE);
+         }
+         ibus_input_context_focus_in (priv->ibuscontext);
++        ibus_wayland_im_reset_modifiers (wlim);
+         g_signal_emit (wlim,
+                        wayland_im_signals[IBUS_FOCUS_IN],
+                        0,
+-- 
+2.54.0
+
+From 22ca37a5d43ac7cf049780da5e1452505406c072 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Thu, 25 Jun 2026 22:16:42 +0900
+Subject: [PATCH 4/5] client/wayland: Fix infinite dead_diaeresis with fr(ergol) keymap
+
+Typing the key <AD09> twice gives different keysyms between KeyPress
+and KeyRelease events and causes the reverse order of KeyPress and
+KeyRelease of the "dead_diaeresis" keysym.
+Now ibus-wayland checks the special order not to cause the key repeating.
+Also ibus-wayland releases the state of "ISO_Level5_Latch" with typing
+the key <AD09> twice.
+
+Fixes: #2869
+Closes: #2894
+---
+ client/wayland/ibuswaylandim.c | 245 +++++++++++++++++++++++----------
+ 1 file changed, 172 insertions(+), 73 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index 97e3ce38..efeb6783 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -71,6 +71,12 @@ typedef enum {
+     IBUS_KEY_ISO_LEVEL_5
+ } IBusKeyIsoLevelValue;
+ 
++typedef enum {
++    IBUS_KEY_ISO_LEVEL_STATE_RELEASE,
++    IBUS_KEY_ISO_LEVEL_STATE_SHIFT,
++    IBUS_KEY_ISO_LEVEL_STATE_LATCH
++} IBusKeyIsoLevelState;
++
+ struct zwp_input_method_context_union {
+     union {
+         struct zwp_input_method_context_v1 *context_v1;
+@@ -170,12 +176,14 @@ struct _IBusWaylandIMPrivate
+ 
+     IBusXkbKeymap key_user;
+     IBusXkbKeymap key_sys;
+-    gboolean is_pressed_mod3;
+-    gboolean is_pressed_mod5;
++    IBusKeyIsoLevelState iso_level3_state;
++    IBusKeyIsoLevelState iso_level5_state;
+ 
+     uint32_t im_serial;
+     int32_t repeat_rate;
+     int32_t repeat_delay;
++    xkb_keysym_t pressed_dead_key;
++    xkb_keysym_t released_dead_key_wo_press;
+ 
+     GCancellable *cancellable;
+ };
+@@ -360,8 +368,10 @@ ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     priv->is_virtual_latch_state = FALSE;
+-    priv->is_pressed_mod3 = FALSE;
+-    priv->is_pressed_mod5 = FALSE;
++    priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
++    priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
++    priv->pressed_dead_key = 0;
++    priv->released_dead_key_wo_press = 0;
+     if (priv->key_user.state && priv->key_sys.state) {
+         group = xkb_state_serialize_layout (priv->key_sys.state,
+                                             XKB_STATE_LAYOUT_LOCKED);
+@@ -407,7 +417,8 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+                                   IBusXkbKeymap  *active_key,
+                                   gboolean        filtered,
+                                   xkb_mod_mask_t *new_mods_depressed,
+-                                  gboolean       *clear_virtual_state)
++                                  gboolean       *clear_virtual_state,
++                                  gboolean       *is_invalid_key)
+ {
+     IBusWaylandIMPrivate *priv;
+     uint32_t code = key + 8;
+@@ -416,9 +427,6 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), filtered);
+     priv = ibus_wayland_im_get_instance_private (wlim);
+ 
+-    if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+-        return filtered;
+-
+     ch = ibus_keyval_to_unicode (sym);
+     if (ch == 0 || g_unichar_iscntrl (ch))
+         ch = xkb_state_key_get_utf32 (active_key->state, code);
+@@ -431,23 +439,28 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+ #  define _IBUS_NO_TEXT_INPUT_MOD_MASK (\
+     IBUS_CONTROL_MASK | IBUS_MOD2_MASK | IBUS_MOD4_MASK)
+ #endif
+-    if ((!filtered && !(modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK) &&
+-         !g_unichar_iscntrl (ch)) || filtered) {
+-        /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
+-         * but forwards the key events to the focused application here.
+-         * Because some applications treat the printable keys as control keys,
+-         * E.g. game apps "hjkl" use the cursor move like VI mode.
+-         * Unfortunately the Wayland input-method protocol does not provide
+-         * the fallback logic after apps handle the key events like GTK3/2
+-         * IM modules. But the input-method always should handle key events
+-         * prior to apps.
+-         */
+-        if (!filtered && !priv->use_sys_keymap) {
+-            gchar buff[8] = { 0, };
+-            g_unichar_to_utf8 (ch, buff);
+-            ibus_wayland_im_commit_text (wlim, buff);
+-            filtered = TRUE;
+-        }
++    if ((state == WL_KEYBOARD_KEY_STATE_RELEASED) ||
++        (modifiers & _IBUS_NO_TEXT_INPUT_MOD_MASK)) {
++        return filtered;
++    }
++#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
++
++    /* In case `use_sys_keymap` is %TRUE, IBus does not commit ASCII chars
++     * but forwards the key events to the focused application here.
++     * Because some applications treat the printable keys as control keys,
++     * E.g. game apps "hjkl" use the cursor move like VI mode.
++     * Unfortunately the Wayland input-method protocol does not provide
++     * the fallback logic after apps handle the key events like GTK3/2
++     * IM modules. But the input-method always should handle key events
++     * prior to apps.
++     */
++    if (!filtered && !g_unichar_iscntrl (ch) && !priv->use_sys_keymap) {
++        gchar buff[8] = { 0, };
++        g_unichar_to_utf8 (ch, buff);
++        ibus_wayland_im_commit_text (wlim, buff);
++        filtered = TRUE;
++    }
++    if (!g_unichar_iscntrl (ch) || IS_DEAD_KEY (sym)) {
+         /* If `filtered` is %TRUE, the keysym can be eaten for the compose
+          * preedit by IBus XKB engine. E.g. AltGr-Shift-V key produces
+          * `Level3_Shift' and `Greek_OMEGA` keysym with "us(symbolic)" keymap.
+@@ -456,19 +469,42 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+          * I'm not clarified with "level3(ralt_switch)" in us XKB keymaps.
+          */
+         if (modifiers & IBUS_MOD3_MASK) {
++            /* With the "fr(ergol)" keymap, Typing the key <AD09> twice
++             * produces the "ISO_Level5_Latch" keysym for the first KeyPress
++             * and the "dead_diaeresis" keysym for the first KeyReleease
++             * because the release key event has MOD3(level5) state.
++             * And the second KeyPress has the "dead_diaeresis" keysym and
++             * the second KeyRelease has the "ISO_Level5_Latch" keysym.
++             * The latch state should be cleared with the dead keys.
++             * I downgrade the latch state to the shift state here and
++             * the shift state will be cleared by the released
++             * "ISO_Level5_Latch" keysym but not the pressed dead keys
++             * because xkb_state_update_key() will revert the MOD3(level5)
++             * state of the depressed xkb_state_serialize_mods()
++             * with the dead keys.
++             */
++            if (priv->iso_level5_state == IBUS_KEY_ISO_LEVEL_STATE_LATCH) {
++                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
++                if (is_invalid_key)
++                    *is_invalid_key = TRUE;
++            } else if (clear_virtual_state) {
++                *clear_virtual_state = TRUE;
++            }
+             if (new_mods_depressed)
+                 *new_mods_depressed &= ~active_key->mod3_mask;
+-            if (clear_virtual_state)
+-                *clear_virtual_state = TRUE;
+         }
+         if (modifiers & IBUS_MOD5_MASK) {
++            if (priv->iso_level3_state == IBUS_KEY_ISO_LEVEL_STATE_LATCH) {
++                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
++                if (is_invalid_key)
++                    *is_invalid_key = TRUE;
++            } else if (clear_virtual_state) {
++                *clear_virtual_state = TRUE;
++            }
+             if (new_mods_depressed)
+                 *new_mods_depressed &= ~active_key->mod5_mask;
+-            if (clear_virtual_state)
+-                *clear_virtual_state = TRUE;
+         }
+     }
+-#undef _IBUS_NO_TEXT_INPUT_MOD_MASK
+     return filtered;
+ }
+ 
+@@ -558,7 +594,8 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+                                           IBusXkbKeymap  *active_key,
+                                           gboolean        filtered,
+                                           xkb_mod_mask_t *mods_depressed,
+-                                          gboolean       *clear_virtual_state)
++                                          gboolean       *clear_virtual_state,
++                                          gboolean       *is_invalid_key)
+ {
+     IBusWaylandIMPrivate *priv;
+     uint32_t code = key + 8;
+@@ -572,6 +609,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     g_assert (active_key);
+     g_assert (mods_depressed);
+     g_assert (clear_virtual_state);
++    g_assert (is_invalid_key);
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     new_mods_depressed = *mods_depressed;
+@@ -597,13 +635,19 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+             switch (sym) {
+             case IBUS_KEY_ISO_Level3_Latch:
++                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_LATCH;
++                new_mods_depressed |= active_key->mod5_mask;
++                break;
+             case IBUS_KEY_ISO_Level3_Shift:
+-                priv->is_pressed_mod5 = TRUE;
++                priv->iso_level3_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
+                 new_mods_depressed |= active_key->mod5_mask;
+                 break;
+             case IBUS_KEY_ISO_Level5_Latch:
++                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_LATCH;
++                new_mods_depressed |= active_key->mod3_mask;
++                break;
+             case IBUS_KEY_ISO_Level5_Shift:
+-                priv->is_pressed_mod3 = TRUE;
++                priv->iso_level5_state = IBUS_KEY_ISO_LEVEL_STATE_SHIFT;
+                 new_mods_depressed |= active_key->mod3_mask;
+                 break;
+             default:
+@@ -614,61 +658,70 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+             filtered = TRUE;
+         } else {
+             IBusKeyIsoLevelValue current_level = IBUS_KEY_ISO_LEVEL_INVALID;
+-            gboolean *is_pressed_current_mod = NULL;
+-            gboolean *is_pressed_reverse_mod = NULL;
+-            xkb_mod_mask_t current_mode_mask = 0;
+-            xkb_mod_mask_t reverse_mode_mask = 0;
++            IBusKeyIsoLevelState *current_state = NULL;
++            IBusKeyIsoLevelState *reverse_state = NULL;
+             switch (sym) {
+             case IBUS_KEY_ISO_Level3_Latch:
+             case IBUS_KEY_ISO_Level3_Shift:
+                 current_level = IBUS_KEY_ISO_LEVEL_3;
+-                is_pressed_current_mod = &priv->is_pressed_mod5;
+-                is_pressed_reverse_mod = &priv->is_pressed_mod3;
+-                current_mode_mask = active_key->mod5_mask;
+-                reverse_mode_mask = active_key->mod3_mask;
++                current_state = &priv->iso_level3_state;
++                reverse_state = &priv->iso_level5_state;
+                 break;
+             case IBUS_KEY_ISO_Level5_Latch:
+             case IBUS_KEY_ISO_Level5_Shift:
+                 current_level = IBUS_KEY_ISO_LEVEL_5;
+-                is_pressed_current_mod = &priv->is_pressed_mod3;
+-                is_pressed_reverse_mod = &priv->is_pressed_mod5;
+-                current_mode_mask = active_key->mod3_mask;
+-                reverse_mode_mask = active_key->mod5_mask;
++                current_state = &priv->iso_level5_state;
++                reverse_state = &priv->iso_level3_state;
+                 break;
+             default:;
+             }
+             g_assert (current_level != IBUS_KEY_ISO_LEVEL_INVALID);
+-            g_assert (is_pressed_current_mod && is_pressed_reverse_mod);
+-            if (G_LIKELY (*is_pressed_current_mod)) {
+-                *is_pressed_current_mod = FALSE;
+-            } else if (*is_pressed_reverse_mod &&
+-                       (new_mods_depressed & active_key->mod3_mask)) {
++            g_assert (current_state && reverse_state);
++            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
++             * with the key <RALT>.
++             *
++             * Handle the "ISO_Level5_Latch" keysym in the "fr(ergol)" keymap.
++             * Do not use keysym but `current_state` because the latch state
++             * can be changed to the shift state with some key conditions.
++             */
++            if (*current_state == IBUS_KEY_ISO_LEVEL_STATE_SHIFT) {
++                *current_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
++                if (current_level == IBUS_KEY_ISO_LEVEL_3)
++                    new_mods_depressed &= ~active_key->mod5_mask;
++                else if (current_level == IBUS_KEY_ISO_LEVEL_5)
++                    new_mods_depressed &= ~active_key->mod3_mask;
++                else
++                    g_assert_not_reached ();
++                if (sym != system_sym)
++                    *clear_virtual_state = TRUE;
++            } else if (!*current_state && *reverse_state &&
++                       (((current_level == IBUS_KEY_ISO_LEVEL_3) &&
++                         (new_mods_depressed & active_key->mod3_mask)) ||
++                        ((current_level == IBUS_KEY_ISO_LEVEL_5) &&
++                         (new_mods_depressed & active_key->mod5_mask))
++                       )) {
+                 /* The unlikely case !priv->is_pressed_mod3 and
+                  * priv->is_pressed_mod5 means that "de(T3)" keymap gives
+                  * the pressed "Level3_Shift" keysym and
+                  * the released "Level5_Latch" keysym for the key <RALT>.
+-                 * Maybe a bug in "de(T3)" so need to clear the MOD5 state
+-                 * by the pressed "Level3_Shift" keysym.
++                 * This case can happen if the normal keysym and shift keysym
++                 * are different.
+                  */
+-                *is_pressed_reverse_mod = FALSE;
+-                new_mods_depressed &= ~reverse_mode_mask;
++                *reverse_state = IBUS_KEY_ISO_LEVEL_STATE_RELEASE;
++                *is_invalid_key = TRUE;
++                if (current_level == IBUS_KEY_ISO_LEVEL_3)
++                    new_mods_depressed &= ~active_key->mod3_mask;
++                else if (current_level == IBUS_KEY_ISO_LEVEL_5)
++                    new_mods_depressed &= ~active_key->mod5_mask;
++                else
++                    g_assert_not_reached ();
+                 *clear_virtual_state = TRUE;
+                 g_debug ("Got a wrong released %s key without the "
+-                         "pressed one. Maybe a bug in XKB.",
++                         "pressed one. Maybe the next Shift state will "
++                         "produce the delayed pressed one.",
+                          current_level == IBUS_KEY_ISO_LEVEL_3 ?
+                                  "Level3" : "Level5");
+             }
+-            /* The "lv(tilde)" keymap gives the "Level3_Shift" keysym
+-             * with the key <RALT>.
+-             */
+-            if ((current_level == IBUS_KEY_ISO_LEVEL_3 &&
+-                 sym == IBUS_KEY_ISO_Level3_Shift) ||
+-                (current_level == IBUS_KEY_ISO_LEVEL_5 &&
+-                 sym == IBUS_KEY_ISO_Level5_Shift)) {
+-                new_mods_depressed &= ~current_mode_mask;
+-                if (sym != system_sym)
+-                    clear_virtual_state = TRUE;
+-            }
+         }
+         break;
+     case IBUS_KEY_Shift_L:
+@@ -707,7 +760,8 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+                                           IBusXkbKeymap *active_key,
+                                           xkb_mod_mask_t mods_depressed,
+                                           xkb_mod_mask_t new_mods_depressed,
+-                                          gboolean       clear_virtual_state)
++                                          gboolean       clear_virtual_state,
++                                          gboolean       is_invalid_key)
+ {
+     IBusWaylandIMPrivate *priv;
+     uint32_t code = key + 8;
+@@ -738,8 +792,9 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+         if (clear_virtual_state)
+             priv->is_virtual_latch_state = FALSE;
+     }
+-    if (!priv->is_virtual_latch_state ||
+-        (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
++    if (G_LIKELY (!is_invalid_key) &&
++        (!priv->is_virtual_latch_state ||
++         (state != WL_KEYBOARD_KEY_STATE_RELEASED))) {
+         xkb_state_update_key (active_key->state, code,
+                               (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+                               ? XKB_KEY_UP : XKB_KEY_DOWN);
+@@ -754,7 +809,8 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+         /* "de(T3)" keymap gives the pressed "Level3_Shift" keysym and
+          * sets both MOD3(Level5) and MOD5(Level3) states after
+          * xkb_state_update_key() is called with the keypress.
+-         * Maybe a bug in "de(T3)" and sets MOD5(Level3) again here.
++         * FIXME: Should set `is_invalid_key` in this case of the "de(T3)"
++         * keymap too not to call xkb_state_update_key() here?
+          */
+         if (G_UNLIKELY (new_mods_depressed != new2_mods_depressed)) {
+             xkb_state_update_mask (active_key->state, new_mods_depressed,
+@@ -1623,6 +1679,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+     IBusXkbKeymap *active_key;
+     xkb_mod_mask_t mods_depressed, new_mods_depressed;
+     gboolean clear_virtual_state = FALSE;
++    gboolean is_invalid_key = FALSE;
+ 
+     g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
+     priv = ibus_wayland_im_get_instance_private (wlim);
+@@ -1658,7 +1715,8 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                          active_key,
+                                                          filtered,
+                                                          &new_mods_depressed,
+-                                                         &clear_virtual_state);
++                                                         &clear_virtual_state,
++                                                         &is_invalid_key);
+     filtered = ibus_wayland_im_commit_key_event (wlim,
+                                                  key,
+                                                  modifiers,
+@@ -1667,14 +1725,16 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                  active_key,
+                                                  filtered,
+                                                  &new_mods_depressed,
+-                                                 &clear_virtual_state);
++                                                 &clear_virtual_state,
++                                                 &is_invalid_key);
+     ibus_wayland_im_update_virtual_xkb_state (wlim,
+                                               key,
+                                               state,
+                                               active_key,
+                                               mods_depressed,
+                                               new_mods_depressed,
+-                                              clear_virtual_state);
++                                              clear_virtual_state,
++                                              is_invalid_key);
+     return filtered;
+ }
+ 
+@@ -2053,6 +2113,21 @@ key_event_check_repeat (IBusWaylandIM       *wlim,
+         g_clear_pointer (&repeating_event.ibus_object_path, g_free);
+         if (!priv->ibuscontext)
+             return FALSE;
++        if (IS_DEAD_KEY (event->sym)) {
++            /* With "fr(ergol)" keymap, in case that key <AD09> is typed
++             * twice, the second pressed keysym is "dead_diaeresis" and
++             * the second released keysym is "ISO_Level5_Latch".
++             * The keysym "dead_diaeresis" should not be auto-repeated
++             * in this case.
++             */
++            if (priv->released_dead_key_wo_press == event->sym) {
++                priv->released_dead_key_wo_press = 0;
++                return TRUE;
++            }
++            priv->pressed_dead_key = event->sym;
++        } else {
++            priv->released_dead_key_wo_press = 0;
++        }
+         source = g_timeout_source_new (priv->repeat_delay);
+         g_source_attach (source, NULL);
+         g_source_unref (source);
+@@ -2066,6 +2141,30 @@ key_event_check_repeat (IBusWaylandIM       *wlim,
+         g_source_set_callback (source, _process_key_event_repeat_delay_cb,
+                                &repeating_event, NULL);
+     } else {
++        if (event->sym != repeating_event.sym) {
++            if (IS_DEAD_KEY (event->sym)) {
++                if (event->sym == priv->pressed_dead_key) {
++                    priv->pressed_dead_key = 0;
++                } else if (!priv->pressed_dead_key) {
++                    /* With "fr(ergol)" keymap, in case that key <AD09> is
++                     * typed twice, the first pressed keysym is
++                     * "ISO_Level5_Latch" and the first released keysym is
++                     * "dead_diaeresis" with the MOD3(the level5) mask,
++                     * the second pressed keysym is "dead_diaeresis" and
++                     * the second released keysym is "ISO_Level5_Latch".
++                     * So the first released "dead_diaeresis" is invalid and
++                     * the second pressed "dead_diaeresis" should be released
++                     * immediately.
++                     */
++                    priv->released_dead_key_wo_press = event->sym;
++                }
++            }
++        } else if (IS_DEAD_KEY (repeating_event.sym)) {
++            if (repeating_event.sym == priv->pressed_dead_key)
++                priv->pressed_dead_key = 0;
++        } else {
++            priv->released_dead_key_wo_press = 0;
++        }
+         if (repeating_event.repeat_rate_id) {
+             g_source_remove (repeating_event.repeat_rate_id);
+             repeating_event.repeat_rate_id = 0;
+-- 
+2.54.0
+
+From b60b54044406ff508fd025de32c10d78829ff2a3 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Sun, 19 Jul 2026 18:15:42 +0900
+Subject: [PATCH 5/5] client/wayland: Fix CapsLock between focus changes
+
+ibus_wayland_im_reset_modifiers() should not reset the state of CapsLock
+and ibus_wayland_im_update_virtual_depressed() should compare
+`priv->modifiers` and `(mods_depressed & mods_locked)`.
+
+Fixes: #2869
+---
+ client/wayland/ibuswaylandim.c | 33 ++++++++++++++++++++++++---------
+ 1 file changed, 24 insertions(+), 9 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index efeb6783..8b1e7b53 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -362,6 +362,7 @@ static void
+ ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
+ {
+     IBusWaylandIMPrivate *priv;
++    xkb_mod_mask_t mods_locked = 0;
+     xkb_layout_index_t  group = 0;
+ 
+     g_assert (IBUS_IS_WAYLAND_IM (wlim));
+@@ -373,11 +374,15 @@ ibus_wayland_im_reset_modifiers (IBusWaylandIM *wlim)
+     priv->pressed_dead_key = 0;
+     priv->released_dead_key_wo_press = 0;
+     if (priv->key_user.state && priv->key_sys.state) {
++        mods_locked = xkb_state_serialize_mods (priv->key_sys.state,
++                                                XKB_STATE_LOCKED);
+         group = xkb_state_serialize_layout (priv->key_sys.state,
+                                             XKB_STATE_LAYOUT_LOCKED);
+-        input_method_keyboard_modifiers (wlim, NULL, 0, 0, 0, 0, group);
++        input_method_keyboard_modifiers (wlim, NULL, 0, 0, 0,
++                                         mods_locked,
++                                         group);
+     } else {
+-        priv->modifiers = 0;
++        priv->modifiers &= IBUS_LOCK_MASK;
+     }
+ }
+ 
+@@ -600,6 +605,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     IBusWaylandIMPrivate *priv;
+     uint32_t code = key + 8;
+     xkb_mod_mask_t new_mods_depressed;
++    xkb_mod_mask_t mods_locked;
+     xkb_keysym_t system_sym = sym;
+ 
+     if (filtered)
+@@ -613,10 +619,13 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     new_mods_depressed = *mods_depressed;
++    mods_locked = xkb_state_serialize_mods (active_key->state,
++                                            XKB_STATE_LOCKED);
+ 
+-    if ((modifiers & ~IBUS_RELEASE_MASK ) != new_mods_depressed) {
++    if ((modifiers & ~IBUS_RELEASE_MASK ) !=
++        (new_mods_depressed | mods_locked)) {
+         g_warning ("IBus modifiers %X is different from XKB depressed %X",
+-                   modifiers, new_mods_depressed);
++                   modifiers, new_mods_depressed | mods_locked);
+     }
+     if (priv->key_sys.state)
+         system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+@@ -772,7 +781,7 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+     mods_locked = xkb_state_serialize_mods (active_key->state,
+-                                            XKB_STATE_MODS_LOCKED);
++                                            XKB_STATE_LOCKED);
+     /* XKB group layout is configured in the system keymap but not the
+      * user keymap which always includes a single layout.
+      */
+@@ -2220,9 +2229,15 @@ input_method_keyboard_key (void                      *data,
+     /* xkb_key_get_syms() does not return the capital syms with Shift key. */
+     if (priv->key_sys.state)
+         event.sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
++    /* The position of Caps_Lock, Control, ISO_Next_Group, Multi_key keysyms
++     * can be customzied by each desktop configuration.
++     */
+     switch (event.sym) {
+-    case IBUS_KEY_Multi_key:
++    case IBUS_KEY_Caps_Lock:
++    case IBUS_KEY_Control_L:
++    case IBUS_KEY_Control_R:
+     case IBUS_KEY_ISO_Next_Group:
++    case IBUS_KEY_Multi_key:
+         break;
+     default:
+         if (priv->key_user.state)
+@@ -2337,11 +2352,11 @@ input_method_keyboard_modifiers (void                      *data,
+         }
+     }
+     if (active_key->state) {
+-        /* CapsLock needs XKB_STATE_MODS_LOCKED */
++        /* CapsLock needs XKB_STATE_LOCKED */
+         mask = xkb_state_serialize_mods (active_key->state,
+                                          XKB_STATE_DEPRESSED |
+                                          XKB_STATE_LATCHED |
+-                                         XKB_STATE_MODS_LOCKED);
++                                         XKB_STATE_LOCKED);
+     }
+     if (priv->verbose) {
+         struct xkb_state *state = active_key->state;
+@@ -2356,7 +2371,7 @@ input_method_keyboard_modifiers (void                      *data,
+                      xkb_state_serialize_mods (state, XKB_STATE_LATCHED) :
+                      0xFFFFFFFF,
+                  state ?
+-                     xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED) :
++                     xkb_state_serialize_mods (state, XKB_STATE_LOCKED) :
+                      0xFFFFFFFF);
+         fflush (priv->log);
+     }
+-- 
+2.54.0
+

diff --git a/ibus.spec b/ibus.spec
index d98f5ce..ccc91a5 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -48,7 +48,7 @@
 Name:           ibus
 Version:        1.5.35~alpha2
 # https://github.com/fedora-infra/rpmautospec/issues/101
-Release:        3%{?dist}
+Release:        4%{?dist}
 Summary:        Intelligent Input Bus for Linux OS
 License:        LGPL-2.1-or-later
 URL:            https://github.com/ibus/%name/wiki
@@ -58,7 +58,6 @@ Source2:        %{name}-xinput
 Source3:        %{name}.conf.5
 # Patch:         %%{name}-HEAD.patch
 Patch:          %{name}-HEAD.patch
-Patch:          %{name}-2444009-wayland-xkb-lv-tilde.patch
 # Under testing #1349148 #1385349 #1350291 #1406699 #1432252 #1601577
 Patch:          %{name}-1385349-segv-bus-proxy.patch
 
@@ -545,6 +544,11 @@ dconf update || :
 %{_datadir}/installed-tests/ibus
 
 %changelog
+* Sun Jul 19 2026 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.35~alpha2-4
+- Fix typo in ibus-compose
+- Merge ibus-2444009-wayland-xkb-lv-tilde.patch
+- Fix CapsLock between focus changes in Wayland
+
 * Thu Jul 16 2026 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.35~alpha2-3
 - Fix CI in GNOME 50
 

                 reply	other threads:[~2026-07-19 10:41 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=178445768491.1.2420955277670842448.rpms-ibus-c99a155c56a1@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