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: Add ibus-2444009-wayland-xkb-lv-tilde.patch
Date: Mon, 01 Jun 2026 08:55:27 GMT	[thread overview]
Message-ID: <178030412710.1.14002144481377255091.rpms-ibus-c32a037db20a@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/ibus
            Branch : rawhide
            Commit : c32a037db20a9a914a74173139c8ac2032d94066
            Author : Takao Fujiwara <tfujiwar@redhat.com>
            Date   : 2026-05-31T11:13:22+09:00
            Stats  : +2786/-0 in 1 file(s)
            URL    : https://src.fedoraproject.org/rpms/ibus/c/c32a037db20a9a914a74173139c8ac2032d94066?branch=rawhide

            Log:
            Add ibus-2444009-wayland-xkb-lv-tilde.patch

Resolves #2444009 Latch key in Latvian(tilde) keymap

---
diff --git a/ibus-2444009-wayland-xkb-lv-tilde.patch b/ibus-2444009-wayland-xkb-lv-tilde.patch
new file mode 100644
index 0000000..04e942c
--- /dev/null
+++ b/ibus-2444009-wayland-xkb-lv-tilde.patch
@@ -0,0 +1,2786 @@
+From 569a9090defbab0600042c795c138bd8fe2370cd Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 18 May 2026 17:05:11 +0900
+Subject: [PATCH 1/9] 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: https://github.com/ibus/ibus/commit/0104486
+Closes: rhbz#2444009
+---
+ client/wayland/ibuswaylandim.c | 279 ++++++++++++++++++++++++++++-----
+ 1 file changed, 243 insertions(+), 36 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index 367b07c5..fa72ce4e 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -39,6 +39,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,
+@@ -125,6 +128,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;
+@@ -195,10 +199,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
+@@ -220,6 +220,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
+@@ -1098,6 +1111,7 @@ ibus_wayland_im_update_xkb_state (IBusWaylandIM     *wlim,
+         xkb_keymap_unref (priv->keymap);
+     priv->keymap = keymap;
+     priv->state = state;
++
+     return TRUE;
+ }
+ 
+@@ -1119,7 +1133,14 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+     g_assert (!g_strcmp0 (ibus_engine_desc_get_name (desc), engine_name));
+     keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+     if (keymap && !ibus_wayland_im_update_xkb_state (wlim, 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->state ? "TRUE" : "FALSE");
++        fflush (priv->log);
++    }
+     g_object_unref (desc);
+ }
+ 
+@@ -1134,6 +1155,8 @@ input_method_keyboard_keymap (void                      *data,
+     IBusWaylandIM *wlim = data;
+     IBusWaylandIMPrivate *priv;
+     struct xkb_keymap *keymap;
++    struct xkb_state *prev_state = NULL;
++    gboolean has_new_state = FALSE;
+ 
+     if (!IBUS_IS_WAYLAND_IM (wlim)) {
+         close (fd);
+@@ -1158,10 +1181,35 @@ input_method_keyboard_keymap (void                      *data,
+         return;
+     }
+     keymap = create_system_xkb_keymap (priv->xkb_context, format, fd, size);
+-    if (keymap && !ibus_wayland_im_update_xkb_state (wlim, keymap))
+-        xkb_keymap_unref (keymap);
+     if (priv->state)
+-        priv->state_system = xkb_state_ref (priv->state);
++        prev_state = xkb_state_ref (priv->state);
++    if (keymap)
++        has_new_state = ibus_wayland_im_update_xkb_state (wlim, keymap);
++    if (priv->state) {
++        if (has_new_state) {
++            priv->state_system = xkb_state_ref (priv->state);
++            /* prev->state is user prev->state_system is system */
++            if (prev_state) {
++                xkb_state_unref (priv->state);
++                priv->state = prev_state;
++            }
++        } else {
++            if (prev_state)
++                xkb_state_unref (prev_state);
++            if (keymap)
++                g_clear_pointer (&keymap, xkb_keymap_unref);
++        }
++    } else if (keymap) {
++        g_clear_pointer (&keymap, xkb_keymap_unref);
++    }
++    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",
++                 priv->state ? "TRUE" : "FALSE");
++        fflush (priv->log);
++    }
+ }
+ 
+ 
+@@ -1199,6 +1247,7 @@ _get_seat_with_name (GPtrArray *seats,
+     return NULL;
+ }
+ 
++
+ static gboolean
+ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                           uint32_t       key,
+@@ -1208,8 +1257,13 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                           gboolean       filtered)
+ {
+     IBusWaylandIMPrivate *priv;
++    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);
+@@ -1229,39 +1283,123 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+     }
+     if (!priv->state)
+         return FALSE;
++    mods_depressed = xkb_state_serialize_mods (priv->state,
++                                               XKB_STATE_DEPRESSED |
++                                               XKB_STATE_LATCHED);
++    new_mods_depressed = mods_depressed;
++    mods_locked = xkb_state_serialize_mods (priv->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->state_system,
++                                        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->state_system, 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 |= priv->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 |= priv->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 |= priv->shift_mask;
++            else
++                new_mods_depressed &= ~priv->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 (!filtered && (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
+-        ch = xkb_state_key_get_utf32 (priv->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 (priv->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) {
++            if (!filtered) {
++                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 &= ~priv->mod3_mask;
++                clear_virtual_state = TRUE;
++            }
++            if (modifiers & IBUS_MOD5_MASK) {
++                new_mods_depressed &= ~priv->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 (priv->state, code,
++                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
++                              ? XKB_KEY_UP : XKB_KEY_DOWN);
+     }
+-    xkb_state_update_key (priv->state, code,
+-                          (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+-                          ? XKB_KEY_UP : XKB_KEY_DOWN);
+     return filtered;
+ }
+ 
+@@ -1706,12 +1844,31 @@ input_method_keyboard_key (void                      *data,
+     /* xkb_key_get_syms() does not return the capital syms with Shift key. */
+     if (priv->state_system)
+         event.sym = xkb_state_key_get_one_sym (priv->state_system, code);
+-    if (event.sym != IBUS_KEY_Multi_key)
++    switch (event.sym) {
++    case IBUS_KEY_Multi_key:
++    case IBUS_KEY_ISO_Next_Group:
++        break;
++    default:
+         event.sym = xkb_state_key_get_one_sym (priv->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->state, code),
++                 xkb_state_key_get_one_sym (priv->state_system, code),
++                 event.modifiers, state ? "press" : "release");
++        fflush (priv->log);
++    }
++#endif
+ 
+     key_event_check_repeat (wlim, &event);
+     switch (_use_sync_mode) {
+@@ -1740,11 +1897,47 @@ input_method_keyboard_modifiers (void                      *data,
+ 
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+     priv = ibus_wayland_im_get_instance_private (wlim);
+-    xkb_state_update_mask (priv->state, mods_depressed,
+-                           mods_latched, mods_locked, 0, 0, group);
++    /* 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->is_virtual_latch_state || key_serial == 0) {
++        xkb_state_update_mask (priv->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->state_system 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.
++         */
++        xkb_state_update_mask (priv->state_system, mods_depressed,
++                               mods_latched, mods_locked, 0, 0, group);
++    }
++    /* CapsLock needs XKB_STATE_MODS_LOCKED */
+     mask = xkb_state_serialize_mods (priv->state,
+                                      XKB_STATE_DEPRESSED |
+-                                     XKB_STATE_LATCHED);
++                                     XKB_STATE_LATCHED |
++                                     XKB_STATE_MODS_LOCKED);
++    if (priv->verbose) {
++        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 (priv->state, XKB_STATE_DEPRESSED),
++                 xkb_state_serialize_mods (priv->state, XKB_STATE_LATCHED),
++                 xkb_state_serialize_mods (priv->state, XKB_STATE_MODS_LOCKED));
++        fflush (priv->log);
++    }
+ 
+     priv->modifiers = 0;
+     if (mask & priv->shift_mask)
+@@ -1770,6 +1963,9 @@ input_method_keyboard_modifiers (void                      *data,
+     if (mask & priv->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,
+@@ -2748,7 +2944,18 @@ ibus_wayland_im_constructor (GType                  type,
+     if (desc)
+         keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+     if (keymap && !ibus_wayland_im_update_xkb_state (wlim, keymap))
+-        xkb_keymap_unref (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->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 1276114d10d70cf8f4e489518cab82bbe3ac59cb Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:36:32 +0900
+Subject: [PATCH 2/9] client/wayland: Refactor ibus_wayland_im_post_key()
+
+Separate 3 functions.
+
+Closes: rhbz#2444009
+---
+ client/wayland/ibuswaylandim.c | 346 +++++++++++++++++++++------------
+ 1 file changed, 227 insertions(+), 119 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index fa72ce4e..425574bd 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -359,6 +359,71 @@ 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,
++                                  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 (priv->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) {
++        if (!filtered) {
++            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 &= ~priv->mod3_mask;
++            if (clear_virtual_state)
++                *clear_virtual_state = TRUE;
++        }
++        if (modifiers & IBUS_MOD5_MASK) {
++            if (new_mods_depressed)
++                *new_mods_depressed &= ~priv->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,
+@@ -423,6 +488,146 @@ 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,
++                                          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 (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->state_system, 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 |= priv->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 |= priv->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 |= priv->shift_mask;
++        else
++            new_mods_depressed &= ~priv->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,
++                                          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 (priv->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->state_system,
++                                        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 (priv->state, code,
++                              (state == WL_KEYBOARD_KEY_STATE_RELEASED)
++                              ? XKB_KEY_UP : XKB_KEY_DOWN);
++    }
++}
++
++
+ static void
+ _context_commit_text_cb (IBusInputContext *context,
+                          IBusText         *text,
+@@ -1257,12 +1462,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                           gboolean       filtered)
+ {
+     IBusWaylandIMPrivate *priv;
+-    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);
+@@ -1287,119 +1487,27 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                XKB_STATE_DEPRESSED |
+                                                XKB_STATE_LATCHED);
+     new_mods_depressed = mods_depressed;
+-    mods_locked = xkb_state_serialize_mods (priv->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->state_system,
+-                                        XKB_STATE_LAYOUT_LOCKED);
+-    if (!filtered) {
+-        system_sym = xkb_state_key_get_one_sym (priv->state_system, 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 |= priv->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 |= priv->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 |= priv->shift_mask;
+-            else
+-                new_mods_depressed &= ~priv->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 (priv->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) {
+-            if (!filtered) {
+-                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 &= ~priv->mod3_mask;
+-                clear_virtual_state = TRUE;
+-            }
+-            if (modifiers & IBUS_MOD5_MASK) {
+-                new_mods_depressed &= ~priv->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 (priv->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,
++                                                         filtered,
++                                                         &new_mods_depressed);
++    filtered = ibus_wayland_im_commit_key_event (wlim,
++                                                 key,
++                                                 modifiers,
++                                                 state,
++                                                 sym,
++                                                 filtered,
++                                                 &new_mods_depressed,
++                                                 &clear_virtual_state);
++    ibus_wayland_im_update_virtual_xkb_state (wlim,
++                                              key,
++                                              state,
++                                              mods_depressed,
++                                              new_mods_depressed,
++                                              clear_virtual_state);
+     return filtered;
+ }
+ 
+-- 
+2.53.0
+
+From 5ec5a8c428cb64be6491be1d765ab6e15dd7c172 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:42:51 +0900
+Subject: [PATCH 3/9] client/wayland: Separate system xkb_state from user one
+
+The modifier masks could be different by keymaps when
+xkb_map_mod_get_index(keymap, ...) is called and now the keymaps
+and states are separated between the system and user.
+
+Also xkb_state_ref() would not be good because the internal keymaps
+are not ref-counted so now use xkb_state_new(xkb_keymap_ref(keymap))
+instead.
+
+Fixes: https://github.com/ibus/ibus/pull/2869#discussion_r3099963386
+---
+ client/wayland/ibuswaylandim.c | 277 ++++++++++++++++++++-------------
+ 1 file changed, 166 insertions(+), 111 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index 425574bd..2c022548 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -98,9 +98,28 @@ struct _IBusWaylandSeat
+     gboolean active;
+     gboolean pending_activate;
+     gboolean pending_deactivate;
+-    gboolean has_keymap;
++    gboolean has_compositor_keymap;
+ };
+ 
++typedef struct _IBusXkbKeymap
++{
++    int32_t fd;
++    struct xkb_keymap *keymap;
++    struct xkb_state *state;
++
++    xkb_mod_mask_t shift_mask;
++    xkb_mod_mask_t lock_mask;
++    xkb_mod_mask_t control_mask;
++    xkb_mod_mask_t mod1_mask;
++    xkb_mod_mask_t mod2_mask;
++    xkb_mod_mask_t mod3_mask;
++    xkb_mod_mask_t mod4_mask;
++    xkb_mod_mask_t mod5_mask;
++    xkb_mod_mask_t super_mask;
++    xkb_mod_mask_t hyper_mask;
++    xkb_mod_mask_t meta_mask;
++} IBusXkbKeymap;
++
+ typedef struct _IBusWaylandIMPrivate IBusWaylandIMPrivate;
+ struct _IBusWaylandIMPrivate
+ {
+@@ -140,21 +159,8 @@ struct _IBusWaylandIMPrivate
+ 
+     struct xkb_context *xkb_context;
+ 
+-    struct xkb_keymap *keymap;
+-    struct xkb_state *state;
+-    struct xkb_state *state_system;
+-
+-    xkb_mod_mask_t shift_mask;
+-    xkb_mod_mask_t lock_mask;
+-    xkb_mod_mask_t control_mask;
+-    xkb_mod_mask_t mod1_mask;
+-    xkb_mod_mask_t mod2_mask;
+-    xkb_mod_mask_t mod3_mask;
+-    xkb_mod_mask_t mod4_mask;
+-    xkb_mod_mask_t mod5_mask;
+-    xkb_mod_mask_t super_mask;
+-    xkb_mod_mask_t hyper_mask;
+-    xkb_mod_mask_t meta_mask;
++    IBusXkbKeymap key_user;
++    IBusXkbKeymap key_sys;
+ 
+     uint32_t im_serial;
+     int32_t repeat_rate;
+@@ -365,6 +371,7 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+                                   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)
+@@ -381,7 +388,7 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+ 
+     ch = ibus_keyval_to_unicode (sym);
+     if (ch == 0 || g_unichar_iscntrl (ch))
+-        ch = xkb_state_key_get_utf32 (priv->state, code);
++        ch = xkb_state_key_get_utf32 (active_key->state, code);
+ 
+ /* No text with Control & Alt & Super keys */
+ #ifndef GDK_WINDOWING_QUARTZ
+@@ -408,13 +415,13 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+          */
+         if (modifiers & IBUS_MOD3_MASK) {
+             if (new_mods_depressed)
+-                *new_mods_depressed &= ~priv->mod3_mask;
++                *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 &= ~priv->mod5_mask;
++                *new_mods_depressed &= ~active_key->mod5_mask;
+             if (clear_virtual_state)
+                 *clear_virtual_state = TRUE;
+         }
+@@ -445,11 +452,11 @@ ibus_wayland_im_key (IBusWaylandIM *wlim,
+     case INPUT_METHOD_V2:
+         /* wlroots/types/wlr_virtual_keyboard_v1.c:virtual_keyboard_key()
+          * returns "Cannot send a keypress before defining a keymap"
+-         * if `has_keymap` is %FALSE.
++         * if `has_compositor_keymap` is %FALSE.
+          */
+         if (!priv->seat)
+             break;
+-        if (priv->seat->has_keymap) {
++        if (priv->seat->has_compositor_keymap) {
+             zwp_virtual_keyboard_v1_key (priv->seat->virtual_keyboard,
+                                          time, key, state);
+         }
+@@ -506,6 +513,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+                                           uint32_t        modifiers,
+                                           uint32_t        state,
+                                           xkb_keysym_t    sym,
++                                          IBusXkbKeymap  *active_key,
+                                           gboolean        filtered,
+                                           xkb_mod_mask_t *mods_depressed)
+ {
+@@ -518,6 +526,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+         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);
+@@ -527,7 +536,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+         g_warning ("IBus modifiers %X is different from XKB depressed %X",
+                    modifiers, new_mods_depressed);
+     }
+-    system_sym = xkb_state_key_get_one_sym (priv->state_system, code);
++    system_sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+     switch (sym) {
+     case IBUS_KEY_ISO_Level2_Latch:
+         filtered = TRUE;
+@@ -538,7 +547,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     case IBUS_KEY_ISO_Level3_Latch:
+     case IBUS_KEY_ISO_Level3_Shift:
+         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-            new_mods_depressed |= priv->mod5_mask;
++            new_mods_depressed |= active_key->mod5_mask;
+             if (sym != system_sym)
+                 priv->is_virtual_latch_state = TRUE;
+             filtered = TRUE;
+@@ -549,7 +558,7 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     case IBUS_KEY_ISO_Level5_Latch:
+     case IBUS_KEY_ISO_Level5_Shift:
+         if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+-            new_mods_depressed |= priv->mod3_mask;
++            new_mods_depressed |= active_key->mod3_mask;
+             if (sym != system_sym)
+                 priv->is_virtual_latch_state = TRUE;
+             filtered = TRUE;
+@@ -558,9 +567,9 @@ ibus_wayland_im_update_virtual_depressed (IBusWaylandIM  *wlim,
+     case IBUS_KEY_Shift_L:
+     case IBUS_KEY_Shift_R:
+         if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+-            new_mods_depressed |= priv->shift_mask;
++            new_mods_depressed |= active_key->shift_mask;
+         else
+-            new_mods_depressed &= ~priv->shift_mask;
++            new_mods_depressed &= ~active_key->shift_mask;
+         break;
+     default:;
+     }
+@@ -588,6 +597,7 @@ 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)
+@@ -600,12 +610,12 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+     g_assert (IBUS_IS_WAYLAND_IM (wlim));
+ 
+     priv = ibus_wayland_im_get_instance_private (wlim);
+-    mods_locked = xkb_state_serialize_mods (priv->state,
++    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->state_system,
++    group = xkb_state_serialize_layout (priv->key_sys.state,
+                                         XKB_STATE_LAYOUT_LOCKED);
+ 
+     if (priv->is_virtual_latch_state) {
+@@ -621,7 +631,7 @@ ibus_wayland_im_update_virtual_xkb_state (IBusWaylandIM *wlim,
+     }
+     if (!priv->is_virtual_latch_state ||
+         (state != WL_KEYBOARD_KEY_STATE_RELEASED)) {
+-        xkb_state_update_key (priv->state, code,
++        xkb_state_update_key (active_key->state, code,
+                               (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+                               ? XKB_KEY_UP : XKB_KEY_DOWN);
+     }
+@@ -1276,47 +1286,53 @@ create_system_xkb_keymap (struct xkb_context *xkb_context,
+ 
+ 
+ static gboolean
+-ibus_wayland_im_update_xkb_state (IBusWaylandIM     *wlim,
+-                                  struct xkb_keymap *keymap)
++ibus_xkb_keymap_update_with_keymap (IBusXkbKeymap     *ibus_keymap,
++                                    struct xkb_keymap *keymap,
++                                    int32_t            fd)
+ {
+-    IBusWaylandIMPrivate *priv;
+     struct xkb_state *state;
+ 
+-    g_return_val_if_fail (IBUS_IS_WAYLAND_IM (wlim), FALSE);
+-    priv = ibus_wayland_im_get_instance_private (wlim);
++    g_return_val_if_fail (ibus_keymap, FALSE);
+     g_return_val_if_fail (keymap, FALSE);
+     g_return_val_if_fail ((state = xkb_state_new (keymap)), FALSE);
+ 
+-    priv->shift_mask =
++    if (ibus_keymap->state)
++        xkb_state_unref (ibus_keymap->state);
++    if (ibus_keymap->keymap) {
++        xkb_keymap_unref (ibus_keymap->keymap);
++        if (ibus_keymap->fd) {
++            close (ibus_keymap->fd);
++            ibus_keymap->fd = 0;
++        }
++    }
++    ibus_keymap->keymap = keymap;
++    ibus_keymap->state = state;
++    if (fd > 0)
++        ibus_keymap->fd = fd;
++
++    ibus_keymap->shift_mask =
+         1 << xkb_map_mod_get_index (keymap, "Shift");
+-    priv->lock_mask =
++    ibus_keymap->lock_mask =
+         1 << xkb_map_mod_get_index (keymap, "Lock");
+-    priv->control_mask =
++    ibus_keymap->control_mask =
+         1 << xkb_map_mod_get_index (keymap, "Control");
+-    priv->mod1_mask =
++    ibus_keymap->mod1_mask =
+         1 << xkb_map_mod_get_index (keymap, "Mod1");
+-    priv->mod2_mask =
++    ibus_keymap->mod2_mask =
+         1 << xkb_map_mod_get_index (keymap, "Mod2");
+-    priv->mod3_mask =
++    ibus_keymap->mod3_mask =
+         1 << xkb_map_mod_get_index (keymap, "Mod3");
+-    priv->mod4_mask =
++    ibus_keymap->mod4_mask =
+         1 << xkb_map_mod_get_index (keymap, "Mod4");
+-    priv->mod5_mask =
++    ibus_keymap->mod5_mask =
+         1 << xkb_map_mod_get_index (keymap, "Mod5");
+-    priv->super_mask =
++    ibus_keymap->super_mask =
+         1 << xkb_map_mod_get_index (keymap, "Super");
+-    priv->hyper_mask =
++    ibus_keymap->hyper_mask =
+         1 << xkb_map_mod_get_index (keymap, "Hyper");
+-    priv->meta_mask =
++    ibus_keymap->meta_mask =
+         1 << xkb_map_mod_get_index (keymap, "Meta");
+ 
+-    if (priv->state)
+-        xkb_state_unref (priv->state);
+-    if (priv->keymap)
+-        xkb_keymap_unref (priv->keymap);
+-    priv->keymap = keymap;
+-    priv->state = state;
+-
+     return TRUE;
+ }
+ 
+@@ -1337,13 +1353,16 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+     g_assert (desc);
+     g_assert (!g_strcmp0 (ibus_engine_desc_get_name (desc), engine_name));
+     keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+-    if (keymap && !ibus_wayland_im_update_xkb_state (wlim, keymap))
++    if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                                       keymap,
++                                                       0)) {
+         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->state ? "TRUE" : "FALSE");
++                 priv->key_user.state ? "TRUE" : "FALSE");
+         fflush (priv->log);
+     }
+     g_object_unref (desc);
+@@ -1360,8 +1379,6 @@ input_method_keyboard_keymap (void                      *data,
+     IBusWaylandIM *wlim = data;
+     IBusWaylandIMPrivate *priv;
+     struct xkb_keymap *keymap;
+-    struct xkb_state *prev_state = NULL;
+-    gboolean has_new_state = FALSE;
+ 
+     if (!IBUS_IS_WAYLAND_IM (wlim)) {
+         close (fd);
+@@ -1377,42 +1394,38 @@ input_method_keyboard_keymap (void                      *data,
+         zwp_virtual_keyboard_v1_keymap (priv->seat->virtual_keyboard,
+                                         format, fd, size);
+         /* wlroots/types/wlr_virtual_keyboard_v1.c:virtual_keyboard_keymap()
+-         * sets %TRUE to `has_keymap`.
++         * sets %TRUE to `has_compositor_keymap`.
+          */
+-        priv->seat->has_keymap = TRUE;
++        priv->seat->has_compositor_keymap = TRUE;
+     }
+-    if (priv->keymap && priv->state && priv->state_system) {
++    if (priv->key_user.keymap && priv->key_user.state && priv->key_sys.state) {
+         close (fd);
+         return;
+     }
+     keymap = create_system_xkb_keymap (priv->xkb_context, format, fd, size);
+-    if (priv->state)
+-        prev_state = xkb_state_ref (priv->state);
+-    if (keymap)
+-        has_new_state = ibus_wayland_im_update_xkb_state (wlim, keymap);
+-    if (priv->state) {
+-        if (has_new_state) {
+-            priv->state_system = xkb_state_ref (priv->state);
+-            /* prev->state is user prev->state_system is system */
+-            if (prev_state) {
+-                xkb_state_unref (priv->state);
+-                priv->state = prev_state;
+-            }
+-        } else {
+-            if (prev_state)
+-                xkb_state_unref (prev_state);
+-            if (keymap)
+-                g_clear_pointer (&keymap, xkb_keymap_unref);
+-        }
+-    } else if (keymap) {
++    if (keymap &&
++        !ibus_xkb_keymap_update_with_keymap (&priv->key_sys,
++                                             xkb_keymap_ref (keymap),
++                                             fd)) {
++        xkb_keymap_unref (keymap);
++        g_clear_pointer (&keymap, xkb_keymap_unref);
++        close (fd);
++    }
++    if (keymap && !priv->key_user.state &&
++        !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                             xkb_keymap_ref (keymap),
++                                             0)) {
++        xkb_keymap_unref (keymap);
+         g_clear_pointer (&keymap, xkb_keymap_unref);
+     }
++    if (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",
+-                 priv->state ? "TRUE" : "FALSE");
++                 priv->key_sys.state ? "TRUE" : "FALSE");
+         fflush (priv->log);
+     }
+ }
+@@ -1462,6 +1475,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                           gboolean       filtered)
+ {
+     IBusWaylandIMPrivate *priv;
++    IBusXkbKeymap *active_key;
+     xkb_mod_mask_t mods_depressed, new_mods_depressed;
+     gboolean clear_virtual_state = FALSE;
+ 
+@@ -1481,9 +1495,13 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+     default:
+         g_assert_not_reached ();
+     }
+-    if (!priv->state)
+-        return FALSE;
+-    mods_depressed = xkb_state_serialize_mods (priv->state,
++    if (priv->use_sys_keymap)
++        active_key = &priv->key_sys;
++    else
++        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;
+@@ -1492,6 +1510,7 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                          modifiers,
+                                                          state,
+                                                          sym,
++                                                         active_key,
+                                                          filtered,
+                                                          &new_mods_depressed);
+     filtered = ibus_wayland_im_commit_key_event (wlim,
+@@ -1499,12 +1518,14 @@ ibus_wayland_im_post_key (IBusWaylandIM *wlim,
+                                                  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);
+@@ -1928,8 +1949,10 @@ input_method_keyboard_key (void                      *data,
+ 
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+     priv = ibus_wayland_im_get_instance_private (wlim);
+-    if (!priv->state)
++    if (!priv->key_user.state) {
++        ibus_wayland_im_key (wlim, key_serial, time, key, state);
+         return;
++    }
+ 
+     if (!priv->ibuscontext) {
+         gboolean retval = ibus_wayland_im_post_key (wlim,
+@@ -1950,14 +1973,14 @@ input_method_keyboard_key (void                      *data,
+     code = key + 8;
+     event.sym = 0;
+     /* xkb_key_get_syms() does not return the capital syms with Shift key. */
+-    if (priv->state_system)
+-        event.sym = xkb_state_key_get_one_sym (priv->state_system, code);
++    if (priv->key_sys.state)
++        event.sym = xkb_state_key_get_one_sym (priv->key_sys.state, code);
+     switch (event.sym) {
+     case IBUS_KEY_Multi_key:
+     case IBUS_KEY_ISO_Next_Group:
+         break;
+     default:
+-        event.sym = xkb_state_key_get_one_sym (priv->state, code);
++        event.sym = xkb_state_key_get_one_sym (priv->key_user.state, code);
+     }
+     event.modifiers = priv->modifiers;
+     if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+@@ -1971,8 +1994,8 @@ input_method_keyboard_key (void                      *data,
+                  G_STRFUNC,
+                  key_serial, time, key,
+                  event.sym,
+-                 xkb_state_key_get_one_sym (priv->state, code),
+-                 xkb_state_key_get_one_sym (priv->state_system, code),
++                 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);
+     }
+@@ -1990,6 +2013,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,
+@@ -2001,10 +2047,15 @@ input_method_keyboard_modifiers (void                      *data,
+ {
+     IBusWaylandIM *wlim = data;
+     IBusWaylandIMPrivate *priv;
++    IBusXkbKeymap *active_key;
+     xkb_mod_mask_t mask;
+ 
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+     priv = ibus_wayland_im_get_instance_private (wlim);
++    if (priv->use_sys_keymap)
++        active_key = &priv->key_sys;
++    else
++        active_key = &priv->key_user;
+     /* 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",
+@@ -2017,58 +2068,59 @@ input_method_keyboard_modifiers (void                      *data,
+      * not to override the state of the "us" keymap.
+      */
+     if (!priv->is_virtual_latch_state || key_serial == 0) {
+-        xkb_state_update_mask (priv->state, mods_depressed,
++        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->state_system to switch the XKB 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.
+          */
+-        xkb_state_update_mask (priv->state_system, mods_depressed,
++        xkb_state_update_mask (priv->key_sys.state, mods_depressed,
+                                mods_latched, mods_locked, 0, 0, group);
+     }
+     /* CapsLock needs XKB_STATE_MODS_LOCKED */
+-    mask = xkb_state_serialize_mods (priv->state,
++    mask = xkb_state_serialize_mods (active_key->state,
+                                      XKB_STATE_DEPRESSED |
+                                      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 (priv->state, XKB_STATE_DEPRESSED),
+-                 xkb_state_serialize_mods (priv->state, XKB_STATE_LATCHED),
+-                 xkb_state_serialize_mods (priv->state, XKB_STATE_MODS_LOCKED));
++                 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;
+-    if (mask & priv->shift_mask)
++    if (mask & active_key->shift_mask)
+         priv->modifiers |= IBUS_SHIFT_MASK;
+-    if (mask & priv->lock_mask)
++    if (mask & active_key->lock_mask)
+         priv->modifiers |= IBUS_LOCK_MASK;
+-    if (mask & priv->control_mask)
++    if (mask & active_key->control_mask)
+         priv->modifiers |= IBUS_CONTROL_MASK;
+-    if (mask & priv->mod1_mask)
++    if (mask & active_key->mod1_mask)
+         priv->modifiers |= IBUS_MOD1_MASK;
+-    if (mask & priv->mod2_mask)
++    if (mask & active_key->mod2_mask)
+         priv->modifiers |= IBUS_MOD2_MASK;
+-    if (mask & priv->mod3_mask)
++    if (mask & active_key->mod3_mask)
+         priv->modifiers |= IBUS_MOD3_MASK;
+-    if (mask & priv->mod4_mask)
++    if (mask & active_key->mod4_mask)
+         priv->modifiers |= IBUS_MOD4_MASK;
+-    if (mask & priv->mod5_mask)
++    if (mask & active_key->mod5_mask)
+         priv->modifiers |= IBUS_MOD5_MASK;
+-    if (mask & priv->super_mask)
++    if (mask & active_key->super_mask)
+         priv->modifiers |= IBUS_SUPER_MASK;
+-    if (mask & priv->hyper_mask)
++    if (mask & active_key->hyper_mask)
+         priv->modifiers |= IBUS_HYPER_MASK;
+-    if (mask & priv->meta_mask)
++    if (mask & active_key->meta_mask)
+         priv->modifiers |= IBUS_META_MASK;
+ 
+     if (!key_serial)
+@@ -2083,11 +2135,11 @@ input_method_keyboard_modifiers (void                      *data,
+     case INPUT_METHOD_V2:
+         /* wlroots/types/wlr_virtual_keyboard_v1.c:virtual_keyboard_modifiers()
+          * returns "Cannot send a modifier state before defining a keymap"
+-         * if `has_keymap` is %FALSE.
++         * if `has_compositor_keymap` is %FALSE.
+          */
+         if (!priv->seat)
+             break;
+-        if (priv->seat->has_keymap) {
++        if (priv->seat->has_compositor_keymap) {
+             zwp_virtual_keyboard_v1_modifiers (priv->seat->virtual_keyboard,
+                                                mods_depressed, mods_latched,
+                                                mods_locked, group);
+@@ -2372,7 +2424,7 @@ input_method_activate (void                               *data,
+         /* Regenerating `virtual_keyboard` causes `size` = 0 in
+          * input_method_keyboard_keymap() with focus changes in Sway session.
+          */
+-        priv->seat->has_keymap = FALSE;
++        priv->seat->has_compositor_keymap = FALSE;
+         priv->seat->keyboard_v2 = zwp_input_method_v2_grab_keyboard (
+                 input_method->u.input_method_v2);
+         zwp_input_method_keyboard_grab_v2_add_listener (
+@@ -3051,8 +3103,11 @@ 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_wayland_im_update_xkb_state (wlim, keymap))
++    if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
++                                                       keymap,
++                                                       0)) {
+         g_clear_pointer (&keymap, xkb_keymap_unref);
++    }
+     if (priv->verbose) {
+         if (!desc) {
+             fprintf (priv->log, "Constructor has No global engine\n");
+@@ -3060,7 +3115,7 @@ ibus_wayland_im_constructor (GType                  type,
+             fprintf (priv->log, "Constructor engine %s keymap:%s state:%s\n",
+                      ibus_engine_desc_get_name (desc),
+                      keymap ? "TRUE" : "FALSE",
+-                     priv->state ? "TRUE" : "FALSE");
++                     priv->key_user.state ? "TRUE" : "FALSE");
+         }
+         fflush (priv->log);
+     }
+-- 
+2.53.0
+
+From 65520b9c20943ff6d3b290b9ca41ed370e94cdf2 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:43:17 +0900
+Subject: [PATCH 4/9] 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 2c022548..cd92a988 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -62,6 +62,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;
+@@ -161,6 +168,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;
+@@ -339,6 +348,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)
+@@ -515,7 +546,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;
+@@ -528,6 +560,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;
+@@ -543,25 +576,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:
+@@ -635,6 +730,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);
++        }
++    }
+ }
+ 
+ 
+@@ -1350,6 +1462,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));
+     keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+@@ -1512,7 +1625,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,
+@@ -2378,6 +2492,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 e81da9e11e6ff3847d6b84fec0cff9fcf1565663 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:43:26 +0900
+Subject: [PATCH 5/9] 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 | 227 +++++++++++++++++++++++----------
+ 1 file changed, 163 insertions(+), 64 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index cd92a988..1e1e272c 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -69,6 +69,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;
+@@ -168,12 +174,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;
+ };
+@@ -358,8 +366,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);
+@@ -405,7 +415,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;
+@@ -414,9 +425,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);
+@@ -429,14 +437,19 @@ 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) {
+-        if (!filtered) {
+-            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
++
++    if (!filtered && !g_unichar_iscntrl (ch)) {
++        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.
+@@ -445,19 +458,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;
+ }
+ 
+@@ -547,7 +583,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;
+@@ -561,6 +598,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;
+@@ -585,13 +623,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:
+@@ -602,61 +646,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:
+@@ -695,7 +748,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;
+@@ -724,8 +778,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);
+@@ -740,7 +795,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,
+@@ -1591,6 +1647,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);
+@@ -1626,7 +1683,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,
+@@ -1635,14 +1693,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;
+ }
+ 
+@@ -2021,6 +2081,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);
+@@ -2034,6 +2109,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
+
+From 66c3b4a02e975c230ed8eaab3dd0311e40da7b7b Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:43:35 +0900
+Subject: [PATCH 6/9] client/wayland: Implement IBusWaylandIM:use-system-keymap
+
+Support to use the session keymap forcibly regardless of IBus XKB
+engines. Also let each application handle ASCII keys since the Wayland
+input-method protocol does not support to handle keys after applications
+handle one.
+---
+ client/wayland/ibuswaylandim.c | 43 ++++++++++++++++++++++++++++++----
+ 1 file changed, 38 insertions(+), 5 deletions(-)
+
+diff --git a/client/wayland/ibuswaylandim.c b/client/wayland/ibuswaylandim.c
+index 1e1e272c..5762f10b 100644
+--- a/client/wayland/ibuswaylandim.c
++++ b/client/wayland/ibuswaylandim.c
+@@ -47,7 +47,8 @@ enum {
+     PROP_BUS,
+     PROP_DISPLAY,
+     PROP_LOG,
+-    PROP_VERBOSE
++    PROP_VERBOSE,
++    PROP_USE_SYS_KEYMAP
+ };
+ 
+ enum {
+@@ -138,6 +139,7 @@ struct _IBusWaylandIMPrivate
+ {
+     FILE *log;
+     gboolean verbose;
++    gboolean use_sys_keymap;
+     struct wl_display *display;
+     IMProtocolVersion version;
+ 
+@@ -443,7 +445,16 @@ ibus_wayland_im_commit_key_event (IBusWaylandIM  *wlim,
+     }
+ #undef _IBUS_NO_TEXT_INPUT_MOD_MASK
+ 
+-    if (!filtered && !g_unichar_iscntrl (ch)) {
++    /* 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);
+@@ -1512,7 +1523,7 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+ {
+     IBusWaylandIMPrivate *priv;
+     IBusEngineDesc *desc;
+-    struct xkb_keymap *keymap;
++    struct xkb_keymap *keymap = NULL;
+ 
+     g_return_if_fail (IBUS_IS_BUS (bus));
+     g_return_if_fail (IBUS_IS_WAYLAND_IM (wlim));
+@@ -1521,7 +1532,8 @@ _bus_global_engine_changed_cb (IBusBus       *bus,
+     ibus_wayland_im_reset_modifiers (wlim);
+     g_assert (desc);
+     g_assert (!g_strcmp0 (ibus_engine_desc_get_name (desc), engine_name));
+-    keymap = create_user_xkb_keymap (priv->xkb_context, desc);
++    if (!priv->use_sys_keymap)
++        keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+     if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
+                                                        keymap,
+                                                        0)) {
+@@ -3179,6 +3191,21 @@ ibus_wayland_im_class_init (IBusWaylandIMClass *class)
+                         FALSE,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ 
++    /**
++     * IBusWaylandIM:use-system-keymap:
++     *
++     * Use system keymap.
++     * %TRUE if the session keymap is used forcibly instead of keymaps of the
++     * IBus XKB engines, otherwise %FALSE.
++     */
++    g_object_class_install_property (gobject_class,
++                    PROP_USE_SYS_KEYMAP,
++                    g_param_spec_boolean ("use-system-keymap",
++                        "use system keymap",
++                        "Use system keymap",
++                        FALSE,
++                        G_PARAM_READWRITE));
++
+     /* install signals */
+     /* this module can call ibus_input_context_focus_in() and the focus-in
+      * signal can reach the IBus panel and this also can call
+@@ -3315,7 +3342,7 @@ ibus_wayland_im_constructor (GType                  type,
+         return NULL;
+     }
+     desc = ibus_bus_get_global_engine (priv->ibusbus);
+-    if (desc)
++    if (desc && !priv->use_sys_keymap)
+         keymap = create_user_xkb_keymap (priv->xkb_context, desc);
+     if (keymap && !ibus_xkb_keymap_update_with_keymap (&priv->key_user,
+                                                        keymap,
+@@ -3408,6 +3435,9 @@ ibus_wayland_im_set_property (IBusWaylandIM *wlim,
+         g_assert (!priv->verbose);
+         priv->verbose = g_value_get_boolean (value);
+         break;
++    case PROP_USE_SYS_KEYMAP:
++        priv->use_sys_keymap = g_value_get_boolean (value);
++        break;
+     default:
+         G_OBJECT_WARN_INVALID_PROPERTY_ID (wlim, prop_id, pspec);
+     }
+@@ -3437,6 +3467,9 @@ ibus_wayland_im_get_property (IBusWaylandIM *wlim,
+     case PROP_VERBOSE:
+         g_value_set_boolean (value, priv->verbose);
+         break;
++    case PROP_USE_SYS_KEYMAP:
++        g_value_set_boolean (value, priv->use_sys_keymap);
++        break;
+     default:
+         G_OBJECT_WARN_INVALID_PROPERTY_ID (wlim, prop_id, pspec);
+     }
+-- 
+2.53.0
+
+From 88463fcfedb017a47e5493726d15510fc7239138 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 17:43:43 +0900
+Subject: [PATCH 7/9] ui/gtk3: Support use-system-keyboard-layout for Wayland
+
+---
+ ui/gtk3/application.vala | 7 +++++++
+ ui/gtk3/panel.vala       | 7 ++++---
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/ui/gtk3/application.vala b/ui/gtk3/application.vala
+index a9962685..d82bcc76 100644
+--- a/ui/gtk3/application.vala
++++ b/ui/gtk3/application.vala
+@@ -39,6 +39,7 @@ class Application {
+ #if USE_GDK_WAYLAND
+     private static ulong m_bus_connected_id;
+     private static ulong m_realize_surface_id;
++    private static ulong m_use_system_keymap_id;
+     private static ulong m_ibus_focus_in_id;
+     private static ulong m_ibus_focus_out_id;
+     private static string m_user;
+@@ -115,6 +116,8 @@ class Application {
+         if (m_wayland_im != null) {
+             m_realize_surface_id = m_panel.realize_surface.connect(
+                     (w, s) => this.set_wayland_surface(s));
++            m_use_system_keymap_id = m_panel.use_system_keymap.connect(
++                    (w, b) => m_wayland_im.use_system_keymap = b);
+             m_ibus_focus_in_id = m_wayland_im.ibus_focus_in.connect(
+                     (w, o) => m_panel.set_wayland_object_path(o));
+             m_ibus_focus_out_id = m_wayland_im.ibus_focus_out.connect(
+@@ -146,6 +149,10 @@ class Application {
+             GLib.SignalHandler.disconnect(m_panel, m_realize_surface_id);
+             m_realize_surface_id = 0;
+         }
++        if (m_use_system_keymap_id != 0) {
++            GLib.SignalHandler.disconnect(m_panel, m_use_system_keymap_id);
++            m_use_system_keymap_id = 0;
++        }
+         if (m_ibus_focus_in_id != 0) {
+             GLib.SignalHandler.disconnect(m_wayland_im, m_ibus_focus_in_id);
+             m_ibus_focus_in_id = 0;
+diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
+index a13b8773..a416a6ef 100644
+--- a/ui/gtk3/panel.vala
++++ b/ui/gtk3/panel.vala
+@@ -104,9 +104,7 @@ class Panel : IBus.PanelService {
+ #if USE_GDK_WAYLAND
+     private string? m_wayland_object_path;
+     public signal void realize_surface(void *surface);
+-    public signal void update_shortcut_keys(
+-            IBus.ProcessKeyEventData[] data,
+-            BindingCommon.KeyEventFuncType ftype);
++    public signal void use_system_keymap(bool use_sys_keymap);
+ #endif
+ 
+     public Panel(IBus.Bus bus,
+@@ -747,6 +745,9 @@ class Panel : IBus.PanelService {
+     private void set_use_system_keyboard_layout() {
+         m_use_system_keyboard_layout =
+                 m_settings_general.get_boolean("use-system-keyboard-layout");
++#if USE_GDK_WAYLAND
++        use_system_keymap(m_use_system_keyboard_layout);
++#endif
+     }
+ 
+     private void set_embed_preedit_text() {
+-- 
+2.53.0
+
+From 07bc6f7a3f13d2113e347fb4c917f12d4a2f0ec1 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 29 May 2026 18:26:54 +0900
+Subject: [PATCH 8/9] src: Update IS_DEAD_KEY()
+
+export IS_DEAD_KEY() in ibusinternal.h for ibus-wayland.
+Add engine/test-dead.py to compare ibusinternal.h and ibuskeysyms.h
+---
+ configure.ac                  |  13 ++++
+ engine/Makefile.am            |  10 ++-
+ engine/meson.build            |  16 ++--
+ engine/test-dead.py           | 136 ++++++++++++++++++++++++++++++++++
+ meson.options                 |   5 ++
+ src/ibuscomposetable.c        |  15 ++--
+ src/ibusenginesimple.c        |   4 +-
+ src/ibusenginesimpleprivate.h |  12 +--
+ src/ibusinternal.h            |  11 ++-
+ 9 files changed, 200 insertions(+), 22 deletions(-)
+ create mode 100755 engine/test-dead.py
+
+diff --git a/configure.ac b/configure.ac
+index a59ebe47..88c6d8fd 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -793,6 +793,18 @@ PKG_CHECK_MODULES(XFIXES,
+     [have_xfixes="no (libXfixes version is lower than 6)"]
+ )
+ 
++# --enable-distro-tests
++AC_ARG_ENABLE(distro-tests,
++    AS_HELP_STRING([--enable-distro-tests],
++                   [Enable to test distro files]),
++    [enable_distro_tests=$enableval],
++    [enable_distro_tests=no]
++)
++AM_CONDITIONAL([ENABLE_DISTRO_TESTS], [test x"$enable_distro_tests" = x"yes"])
++if test x"$enable_distro_tests" = x"no"; then
++    enable_distro_tests="no (disabled, use --enable-distro-tests to enable)"
++fi
++
+ # --enable-install-tests
+ AC_ARG_ENABLE(install-tests,
+     AS_HELP_STRING([--enable-install-tests],
+@@ -1069,6 +1081,7 @@ Build options:
+   XFixes client disconnect      $have_xfixes
+   Install systemd service       $enable_systemd
+   Run test cases                $enable_tests
++  Distro tests                  $enable_distro_tests
+   Install tests                 $enable_install_tests
+ ])
+ AS_IF([test $HAS_OUTPUT_TAIL -eq 1],
+diff --git a/engine/Makefile.am b/engine/Makefile.am
+index 3e4d045f..02d65c26 100644
+--- a/engine/Makefile.am
++++ b/engine/Makefile.am
+@@ -4,7 +4,7 @@
+ #
+ # Copyright (c) 2010-2016, Google Inc. All rights reserved.
+ # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2013-2025 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2013-2026 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ #
+ # This library is free software; you can redistribute it and/or
+ # modify it under the terms of the GNU Lesser General Public
+@@ -57,11 +57,18 @@ AM_VALAFLAGS = \
+     --target-glib="$(VALA_TARGET_GLIB_VERSION)" \
+     $(NULL)
+ 
++TESTS_ENVIRONMENT = \
++    lib_srcdir=$(srcdir)/../src \
++    $(NULL)
++
+ TESTS =
+ 
++if ENABLE_DISTRO_TESTS
++TESTS += test-dead.py
+ if ENABLE_PYGNOME_DESKTOP
+ TESTS += test-gnome.py
+ endif
++endif
+ 
+ libexec_PROGRAMS = \
+     ibus-engine-simple \
+@@ -99,6 +106,7 @@ EXTRA_DIST = \
+     gensimple.py \
+     iso639converter.py \
+     simple.xml.in \
++    test-dead.py \
+     test-gnome.py \
+     $(NULL)
+ 
+diff --git a/engine/meson.build b/engine/meson.build
+index a6ab5b4e..887c9c42 100644
+--- a/engine/meson.build
++++ b/engine/meson.build
+@@ -40,17 +40,21 @@ simple_xml = custom_target('simple.xml',
+   install_dir: pkgdatadir / 'component',
+ )
+ 
+-if get_option('tests')
+-  pyoverrides_tests = [
+-    {
+-      'name': 'test-gnome',
+-    },
++disrro_tests_env = {
++  'lib_srcdir': source_root / 'src',
++}
++
++if get_option('tests') and get_option('distro-tests')
++  distro_tests = [
++    { 'name': 'test-dead', },
++    { 'name': 'test-gnome', },
+   ]
+ 
+-  foreach _test : pyoverrides_tests
++  foreach _test : distro_tests
+     timeout = _test.get('timeout', 30)
+     test(_test['name'], files([python.full_path()])[0],
+       args: [ meson.current_source_dir() / '@0@.py'.format(_test['name']) ],
++      env: environment(disrro_tests_env),
+       suite: 'engine',
+       timeout: timeout,
+     )
+diff --git a/engine/test-dead.py b/engine/test-dead.py
+new file mode 100755
+index 00000000..4f351ee8
+--- /dev/null
++++ b/engine/test-dead.py
+@@ -0,0 +1,136 @@
++#!/usr/bin/python3
++# vim:set fileencoding=utf-8 et sts=4 sw=4:
++#
++# ibus - Intelligent Input Bus for Linux / Unix OS
++#
++# Copyright © 2026 Takao Fujiwara <takao.fujiwara1@gmail.com>
++#
++# This library is free software; you can redistribute it and/or
++# modify it under the terms of the GNU Lesser General Public
++# License as published by the Free Software Foundation; either
++# version 2.1 of the License, or (at your option) any later version.
++#
++# This library is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++# Lesser General Public License for more details.
++#
++# You should have received a copy of the GNU Lesser General Public
++# License along with this library. If not, see <http://www.gnu.org/licenses/>.
++
++# Check src/ibusinternal.h:IS_DEAD_KEY()
++
++import os, sys
++from pathlib import Path
++
++class TestDeadKey:
++    __srcdir = None
++    __prgname = None
++    __is_dead_key_file = Path('ibusinternal.h')
++    __dead_keys_file  = Path('ibuskeysyms.h')
++    __max_dead_line = None
++    __max_dead_key  = None
++    __max_dead_val = 0
++    __min_dead_line = None
++    __min_dead_key  = None
++    __min_dead_val = 0xffff
++
++    @classmethod
++    def parse_args(cls):
++        arg0 = Path(sys.argv[0])
++        cls.__srcdir = arg0.parent.parent
++        cls.__prgname = arg0.name
++        lib_srcdir = os.getenv('lib_srcdir')
++        if len(sys.argv) > 1:
++            cls.__srcdir = Path(sys.argv[1])
++        elif lib_srcdir != None:
++            cls.__srcdir = Path(lib_srcdir)
++        if str(cls.__srcdir) == '':
++            cls.__srcdir = Path('.')
++
++    def tests(self):
++        print('TAP version 14')
++        print('1..1')
++        dead_keys_path = self.__srcdir / self.__dead_keys_file
++        if not dead_keys_path.exists():
++            print(f'fail Not found {str(dead_keys_path)}', file=sys.stderr)
++            sys.exit(1)
++        is_dead_key_path = self.__srcdir / self.__is_dead_key_file
++        if not is_dead_key_path.exists():
++            print(f'fail Not found {str(is_dead_key_path)}', file=sys.stderr)
++            sys.exit(1)
++        self.read_dead_keys_file(dead_keys_path)
++        if self.read_is_dead_key_file(is_dead_key_path):
++            print(f'ok 1 /{self.__prgname}')
++        else:
++            sys.exit(1)
++
++    def read_dead_keys_file(self, dead_keys_path):
++        with dead_keys_path.open() as f:
++            for line in f:
++                elements = line.split()
++                if len(elements) < 3:
++                    continue
++                if elements[0] != '#define':
++                    continue
++                if not elements[1].startswith('IBUS_KEY_dead'):
++                    continue
++                val = int(elements[2], 16)
++                if self.__max_dead_val < val:
++                    self.__max_dead_val = val
++                    self.__max_dead_line = line
++                    self.__max_dead_key = elements[1]
++                if self.__min_dead_val > val:
++                    self.__min_dead_val = val
++                    self.__min_dead_line = line
++                    self.__min_dead_key = elements[1]
++        if self.__min_dead_val >= self.__max_dead_val:
++            print(f'fail {self.__min_dead_line} < {self.__max_dead_line}',
++                  file=sys.stderr)
++            sys.exit(1)
++
++    def read_is_dead_key_file(self, is_dead_key_path):
++        has_definition = False
++        min_key = ''
++        max_key = ''
++        with is_dead_key_path.open() as f:
++            for line in f:
++                elements = line.split()
++                if not has_definition:
++                    if len(elements) < 2:
++                        continue
++                    if elements[0] != '#define':
++                        continue
++                    if elements[1].startswith('IS_DEAD_KEY('):
++                        has_definition = True
++                # ((k) >= IBUS_KEY_dead_min && (k) <= IBUS_KEY_dead_max)
++                else:
++                    if len(elements) < 7:
++                        continue
++                    min_key = elements[2]
++                    max_key = elements[6]
++                    if max_key[len(max_key) - 1] == ')':
++                        max_key = max_key[:len(max_key) - 1]
++                    break
++        is_failed = False
++        print(f'# min: {min_key} < max: {max_key}')
++        if self.__min_dead_key != min_key:
++            print(f'fail min of IS_DEAD_KEY() is %s but min in %s is %s' % \
++                  (min_key, str(self.__dead_keys_file), self.__min_dead_key),
++                  file=sys.stderr)
++            is_failed = True
++        if self.__max_dead_key != max_key:
++            print(f'fail max of IS_DEAD_KEY() is %s but max in %s is %s' % \
++                  (max_key, str(self.__dead_keys_file), self.__max_dead_key),
++                  file=sys.stderr)
++            is_failed = True
++        return not is_failed
++
++
++def main():
++    TestDeadKey.parse_args()
++    obj = TestDeadKey()
++    obj.tests()
++    sys.exit(0)
++
++main()
+diff --git a/meson.options b/meson.options
+index 8aab81ef..b5932044 100644
+--- a/meson.options
++++ b/meson.options
+@@ -3,6 +3,11 @@ option('tests',
+   value: true,
+   description: 'Build tests',
+ )
++option('distro-tests',
++  type: 'boolean',
++  value: false,
++  description: 'Enable to test distro files',
++)
+ option('tests-exclude-suites',
+   type: 'string',
+   value: '',
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index 93262f3a..24096a70 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -36,6 +36,7 @@
+ #include "ibustypes.h"
+ 
+ #include "ibusenginesimpleprivate.h"
++#include "ibusinternal.h"
+ 
+ 
+ #define IBUS_COMPOSE_TABLE_MAGIC "IBusComposeTable"
+@@ -101,8 +102,8 @@ parse_compose_value (IBusComposeData  *compose_data,
+                      const char       *val,
+                      const char       *line)
+ {
+-    char *head, *end, *p;
+-    char *ustr = NULL;
++    const char *head, *end, *p;
++    char *ustr = NULL, *comment;
+     gunichar *uchars = NULL, *up;
+     GError *error = NULL;
+     int n_uchars = 0;
+@@ -165,7 +166,9 @@ parse_compose_value (IBusComposeData  *compose_data,
+ 
+     g_free (ustr);
+     g_free (uchars);
+-    compose_data->comment = g_strdup (g_strstrip (end + 1));
++    comment = g_strdup (end + 1);
++    compose_data->comment = g_strdup (g_strstrip (comment));
++    g_free (comment);
+ 
+     return TRUE;
+ 
+@@ -1991,7 +1994,7 @@ ibus_compose_table_check (const IBusComposeTableEx *table,
+     int row_stride = table->max_seq_len + 2;
+     const guint16 *data_first;
+     int n_seqs;
+-    guint16 *seq;
++    const guint16 *seq, *prev_seq;
+ 
+     if (compose_finish)
+         *compose_finish = FALSE;
+@@ -2020,8 +2023,6 @@ ibus_compose_table_check (const IBusComposeTableEx *table,
+     if (seq == NULL)
+         return FALSE;
+ 
+-    guint16 *prev_seq;
+-
+     /* Back up to the first sequence that matches to make sure
+      * we find the exact match if their is one.
+      */
+@@ -2034,7 +2035,7 @@ ibus_compose_table_check (const IBusComposeTableEx *table,
+ 
+     /* complete sequence */
+     if (n_compose == table->max_seq_len || seq[n_compose] == 0) {
+-        guint16 *next_seq;
++        const guint16 *next_seq;
+         gunichar value = 0;
+         int num = 0;
+         int index = 0;
+diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
+index 1eeccd52..c05e281c 100644
+--- a/src/ibusenginesimple.c
++++ b/src/ibusenginesimple.c
+@@ -28,12 +28,14 @@
+ #include "ibuscomposetable.h"
+ #include "ibusemoji.h"
+ #include "ibusenginesimple.h"
+-#include "ibusenginesimpleprivate.h"
+ 
+ #include "ibuskeys.h"
+ #include "ibuskeysyms.h"
+ #include "ibusutil.h"
+ 
++#include "ibusenginesimpleprivate.h"
++#include "ibusinternal.h"
++
+ #include <memory.h>
+ #include <stdlib.h>
+ #include <glib/gi18n-lib.h>
+diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
+index c3f92920..c63522fe 100644
+--- a/src/ibusenginesimpleprivate.h
++++ b/src/ibusenginesimpleprivate.h
+@@ -35,12 +35,6 @@ G_BEGIN_DECLS
+  */
+ #define IBUS_COMPOSE_ERROR (ibus_compose_error_quark ())
+ 
+-/* Checks if a keysym is a dead key. Dead key keysym values are defined in
+- * ibuskeysyms.h and the first is GDK_KEY_dead_grave.
+- */
+-#define IS_DEAD_KEY(k) \
+-      ((k) >= IBUS_KEY_dead_grave && (k) <= IBUS_KEY_dead_greek)
+-
+ 
+ struct _IBusComposeTablePrivate
+ {
+@@ -57,8 +51,10 @@ struct _IBusComposeTablePrivate
+  * Since: 1.5.33
+  * Stability: Unstable
+  */
++G_GNUC_INTERNAL
+ GQuark   ibus_compose_error_quark   (void);
+ guint    ibus_compose_key_flag      (guint                       key);
++G_GNUC_INTERNAL
+ gboolean ibus_check_algorithmically (const guint                *compose_buffer,
+                                      int                         n_compose,
+                                      gunichar                   *output);
+@@ -67,11 +63,13 @@ GVariant *
+                                     (IBusComposeTableEx
+                                                                 *compose_table,
+                                      gboolean                   reverse_endian);
++G_GNUC_INTERNAL
+ IBusComposeTableEx *
+          ibus_compose_table_deserialize
+                                     (const char                 *contents,
+                                      gsize                       length,
+                                      guint16                    *saved_version);
++G_GNUC_INTERNAL
+ gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
+                                      guint                      *compose_buffer,
+                                      int                         n_compose,
+@@ -79,6 +77,7 @@ gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
+                                      gboolean                   *compose_match,
+                                      GString                    *output,
+                                      gboolean                    is_32bit);
++G_GNUC_INTERNAL
+ gunichar ibus_keysym_to_unicode     (guint                       keysym,
+                                      gboolean                    combining,
+                                      gboolean                   *need_space);
+@@ -88,6 +87,7 @@ gunichar ibus_keysym_to_unicode     (guint                       keysym,
+  * Since: 1.5.33
+  * Stability: Unstable
+  */
++G_GNUC_INTERNAL
+ gunichar ibus_keysym_to_unicode_with_layout
+                                     (guint                       keysym,
+                                      gboolean                    combining,
+diff --git a/src/ibusinternal.h b/src/ibusinternal.h
+index c2637ab5..04553236 100644
+--- a/src/ibusinternal.h
++++ b/src/ibusinternal.h
+@@ -62,5 +62,14 @@
+ G_GNUC_INTERNAL void
+ ibus_g_variant_get_child_string (GVariant *variant, gsize index, char **str);
+ 
+-#endif
++#ifdef IBUS_KEY_dead_grave
++#ifdef IBUS_KEY_dead_longsolidusoverlay
++/* Checks if a keysym is a dead key. Dead key keysym values are defined in
++ * ibuskeysyms.h and the first is GDK_KEY_dead_grave.
++ */
++#define IS_DEAD_KEY(k) \
++      ((k) >= IBUS_KEY_dead_grave && (k) <= IBUS_KEY_dead_longsolidusoverlay)
++#endif /* IBUS_KEY_dead_longsolidusoverlay */
++#endif /* IBUS_KEY_dead_grave */
+ 
++#endif
+-- 
+2.53.0
+

                 reply	other threads:[~2026-06-01  8:55 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=178030412710.1.14002144481377255091.rpms-ibus-c32a037db20a@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