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