Skip to content

Random keyboard freeze in X11 apps: xcb-imdkit sync mode stuck forever when XIM_DESTROY_IC races with an in-flight XIM_SYNC_REPLY #1641

Description

@chinese-wzq

Summary

在 X11 会话下使用 fcitx5(双拼 + rime)时,客户端应用(Unreal Engine 5.8.1,内置 SDL 3.4.4,经 Xlib XIM)会随机发生"键盘永久失灵":从某一刻起,所有按键都被输入法吞掉,应用一个键都收不到,直到重启UE5。

根因定位在 xcb-imdkit_xcb_im_handle_sync_reply):当 sync mode 开启时,若客户端在"回灌键在途(client->sync == true)"的窗口内销毁 XIM IC,客户端随后回送的 XIM_SYNC_REPLY 会因 HASH_FIND 找不到已销毁的 IC 而走 if (!ic) break 提前退出,client->sync 永不复位。此后该客户端的所有按键都进 _xcb_im_add_queue 队列且永不派发 → 客户端 XFilterEvent 永远返回 True → 所有按键被吞。

诚实声明:本次bug定位主要由 AI 辅助完成(我提供日志与操作,AI 做日志对齐、源码定位与补丁撰写)。下方证据均为真实运行日志的原文摘录,时间戳为运行现场数据,未做任何编造,绝非AI幻觉;补丁全部经过构建与实测验证。

已验证的事实

  1. 客户端(shim)日志与 fcitx5 调试日志通过用户提供的锚点完成时间对齐(3 个独立匹配点,计算误差约 1.4–1.7ms;受时间戳精度限制可能存在一定误差,不能严格保证),分界点定位到毫秒级。
  2. 对fcitx5应用 workaround(关闭 sync mode)或对xcb-imdkit应用参考补丁后,长时间使用不再遭遇键盘失灵

Environment

  • 系统:Arch Linux(KDE Plasma Wayland 会话 + XWayland :0),X11 模式
  • 输入法:fcitx5 5.1.21(双拼 + rime 引擎),XIM 前端
  • 客户端:Unreal Engine 5.8.1 内置 SDL 3.4.4(SDL 3.4.4 中 XIM 集成在 src/video/x11/SDL_x11events.cXFilterEvent 返回 True 即丢弃按键)
  • xcb-imdkit:仓库 master(与 fcitx5 5.1.21 依赖版本一致的协议实现)

Steps to Reproduce

坦白说明:该 bug 难以主动复现,我们暂未发现可靠的主动复现方法,实测表现为"在 UE 中工作一段时间后随机发生"。步骤如下:

  1. 以 X11 模式启动 UE 5.8.1(通过环境变量 SDL_VIDEODRIVER=x11),开启 XIM 输入法(fcitx5)。
  2. 正常使用键盘输入(含中文输入),持续工作一段时间
  3. 失灵一旦发生即为永久性:从某一刻起所有按键被输入法吞掉,应用收不到任何键,包括纯英文输入,输入法也无法在中文模式下再输入时弹出候选框,直至重启 UE 5.8.1。

由于触发机制是竞争窗口,因此该 bug 为随机、非必现。

Expected Behavior

销毁 IC 不应影响同一连接上后续按键的正常处理;即使 SYNC_REPLY 到达时 IC 已不存在,也不应让 client->sync 永不复位为 false

Output of fcitx5-diagnose command

点击此处展开

System Info:

  1. uname -a:

    Linux human-computer 7.1.7-zen1-1-zen-directsync #1 ZEN SMP PREEMPT_DYNAMIC Tue, 11 Aug 2026 03:13:53 +0000 x86_64 GNU/Linux
    
  2. lsb_release -a:

    LSB Version:    n/a
    Distributor ID: Arch
    Description:    Arch Linux
    Release:        rolling
    Codename:       n/a
    
  3. lsb_release -d:

    Description:    Arch Linux
    
  4. /etc/lsb-release:

    DISTRIB_ID="Arch"
    DISTRIB_RELEASE="rolling"
    DISTRIB_DESCRIPTION="Arch Linux"
    
  5. /etc/os-release:

    NAME="Arch Linux"
    PRETTY_NAME="Arch Linux"
    ID=arch
    BUILD_ID=rolling
    ANSI_COLOR="38;2;23;147;209"
    HOME_URL="https://archlinux.org/"
    DOCUMENTATION_URL="https://wiki.archlinux.org/"
    SUPPORT_URL="https://bbs.archlinux.org/"
    BUG_REPORT_URL="https://gitlab.archlinux.org/groups/archlinux/-/issues"
    PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
    LOGO=archlinux-logo
    
  6. Desktop Environment:

    Desktop environment is kde.

  7. XDG SESSION TYPE:

    XDG_SESSION_TYPE='wayland'
    
  8. Bash Version:

    BASH_VERSION='5.3.15(1)-release'
    

Environment:

  1. DISPLAY:

    DISPLAY=':0'
    
    
    WAYLAND_DISPLAY='wayland-0'
    
  2. Keyboard Layout:

    1. setxkbmap:

      WARNING: Running setxkbmap against an Xwayland server
      xkb_keymap {
          xkb_keycodes  { include "evdev+aliases(qwerty)" };
          xkb_types     { include "complete"      };
          xkb_compat    { include "complete"      };
          xkb_symbols   { include "pc+us+inet(evdev)"     };
          xkb_geometry  { include "pc(pc105)"     };
      };
      
    2. xprop:

      _XKB_RULES_NAMES(STRING) = "evdev", "pc105", "us", "", ""
      
  3. Locale:

    1. All locales:

      C
      C.utf8
      en_US.utf8
      POSIX
      zh_CN.utf8
      
    2. Current locale:

      LANG=zh_CN.UTF-8
      LC_CTYPE="zh_CN.UTF-8"
      LC_NUMERIC=zh_CN.UTF-8
      LC_TIME=zh_CN.UTF-8
      LC_COLLATE="zh_CN.UTF-8"
      LC_MONETARY=zh_CN.UTF-8
      LC_MESSAGES="zh_CN.UTF-8"
      LC_PAPER=zh_CN.UTF-8
      LC_NAME=zh_CN.UTF-8
      LC_ADDRESS=zh_CN.UTF-8
      LC_TELEPHONE=zh_CN.UTF-8
      LC_MEASUREMENT=zh_CN.UTF-8
      LC_IDENTIFICATION="zh_CN.UTF-8"
      LC_ALL=
      
  4. Directories:

    1. Home:

      /home/human
      
    2. ${XDG_CONFIG_HOME}:

      Environment variable XDG_CONFIG_HOME is not set.

      Current value of XDG_CONFIG_HOME is ~/.config (/home/human/.config).

    3. Fcitx5 Settings Directory:

      Current fcitx5 settings directory is ~/.config/fcitx5 (/home/human/.config/fcitx5).

  5. Current user:

    The script is run as human (1000).

Fcitx State:

  1. executable:

    Found fcitx5 at /usr/bin/fcitx5.

  2. version:

    Fcitx version: 5.1.21

  3. process:

    Found 2 fcitx5 processes:

       1533 fcitx5-wayland-
       1536 fcitx5
    
  4. fcitx5-remote:

    fcitx5-remote works properly.

  5. DBus interface:

    Using dbus-send to check dbus.

    Owner of DBus name org.fcitx.Fcitx5 is :1.15.

    PID of DBus name org.fcitx.Fcitx5 owner is 1536.

    Debug information from dbus:

       Group [x11::0] has 2 InputContext(s)
      IC [0a5106911167463ea14ccb6fcd672fb3] program:UnrealEditor frontend:xim cap:4000000000 focus:0
      IC [cd5e7056b902438a9d82e5f84328f827] program:UnrealEditor frontend:xim cap:4000000000 focus:0
    Group [wayland:] has 10 InputContext(s)
      IC [33dcd6d8fde1418dae4889363b74b418] program:google-chrome frontend:wayland cap:80072 focus:0
      IC [1a3e27e6634a4ed5a3a9624cb5e80f12] program:chrome-home frontend:wayland cap:80072 focus:0
      IC [68b3e33f9f96449f95797a7a3126c592] program:zcode frontend:wayland cap:8000d0072 focus:0
      IC [2bdd7fbb16bc4c7fb8378b3bea45a31d] program:vlc frontend:wayland cap:80072 focus:0
      IC [e39c5357347f49c1aa515f3d6f1421d7] program:open-orpheus frontend:wayland cap:90072 focus:0
      IC [fa9220d1f28845e2b14a11f6fdcc8ca1] program:org.kde.konsole frontend:wayland cap:72 focus:1
      IC [1df71681da0f4154a5f633d496f84133] program:com.epicgames.UnrealEngineEditor frontend:wayland cap:8000d0072 focus:0
      IC [b40273f66a0a44a5879a4f31c1bcab3a] program:org.kde.dolphin frontend:wayland cap:72 focus:0
      IC [97f37c7b0fea43e294fd1bc39b586061] program:org.telegram.desktop frontend:wayland cap:8000d0072 focus:0
      IC [caf17f3f4c7342838f147930b82ab16c] program: frontend:wayland cap:72 focus:0
    Input Context without group
    

Frontends setup:

The environment variable checked by this script only shows the environment under current shell. It is still possible that you did not set the environment to the whole graphic desktop session. You may inspect the actual environment variable of a certain process by using xargs -0 -L1 /proc/$PID/environ for a certain process that you find not working.

Xim:

  1. ${XMODIFIERS}:

    Environment variable XMODIFIERS is set to "@im=fcitx" correctly.
    Xim Server Name from Environment variable is fcitx.

  2. XIM_SERVERS on root window:

    Xim server name is the same with that set in the environment variable.

Qt:

  1. qt4 - ${QT4_IM_MODULE}:

    Please set environment variable QT_IM_MODULE to "fcitx" using the tool your distribution provides or add export QT_IM_MODULE=fcitx to your ~/.xprofile. See Input Method Related Environment Variables: QT_IM_MODULE.

    fcitx5-qt4-immodule-probing not found.

  2. qt5 - ${QT_IM_MODULE}:

    Please set environment variable QT_IM_MODULE to "fcitx" using the tool your distribution provides or add export QT_IM_MODULE=fcitx to your ~/.xprofile. See Input Method Related Environment Variables: QT_IM_MODULE.

    It is OK to use qt5 built-in Wayland im module if your compositor fully supports text-input protocol used by qt5.

    Using fcitx5-qt5-immodule-probing to check the actual im module to be used under current environment:

    QT_QPA_PLATFORM=wayland
    QT_IM_MODULE=
    IM_MODULE_CLASSNAME=QtWaylandClient::QWaylandInputContext
    
  3. qt6 - ${QT_IM_MODULE}:

    Please set environment variable QT_IM_MODULE to "fcitx" using the tool your distribution provides or add export QT_IM_MODULE=fcitx to your ~/.xprofile. See Input Method Related Environment Variables: QT_IM_MODULE.

    It is OK to use qt6 built-in Wayland im module if your compositor fully supports text-input protocol used by qt6.

    Using fcitx5-qt6-immodule-probing to check the actual im module to be used under current environment:

    QT_QPA_PLATFORM=wayland
    QT_IM_MODULE=
    IM_MODULE_CLASSNAME=QtWaylandClient::QWaylandInputContext
    
  4. Qt IM module files:

    Found fcitx5 im module for qt6: /usr/lib/qt6/plugins/platforminputcontexts/libfcitx5platforminputcontextplugin.so.
    Found unknown fcitx qt module: /usr/lib/qt6/plugins/plasma/kcms/systemsettings/kcm_fcitx5.so.
    Found fcitx5 im module for qt: /usr/lib/qt/plugins/platforminputcontexts/libfcitx5platforminputcontextplugin.so.
    Found fcitx5 qt6 module: /usr/lib/fcitx5/qt6/libfcitx-quickphrase-editor5.so.
    Found unknown fcitx qt module: /usr/lib/office6/qt/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so.

    Following error may not be accurate because guessing Qt version from path depends on how your distribution packages Qt. It is not a critical error if you do not use any Qt application with certain version of Qt or you are using text-input support by Qt under Wayland.
    Cannot find fcitx5 input method module for Qt4.

Gtk:

  1. gtk - ${GTK_IM_MODULE}:

    Please set environment variable GTK_IM_MODULE to "fcitx" using the tool your distribution provides or add export GTK_IM_MODULE=fcitx to your ~/.xprofile. See Input Method Related Environment Variables: GTK_IM_MODULE.

    It is OK to use gtk built-in Wayland im module if your compositor fully supports text-input protocol used by gtk.

    fcitx5-gtk2-immodule-probing not found.

    Using fcitx5-gtk3-immodule-probing to check the actual im module to be used under current environment:

    GTK_IM_MODULE=wayland
    

    Using fcitx5-gtk4-immodule-probing to check the actual im module to be used under current environment:

    GTK_IM_MODULE=wayland
    
  2. gtk-query-immodules:

    1. gtk 2:

      Found gtk-query-immodules for gtk 2.24.33 at /usr/bin/gtk-query-immodules-2.0.
      Version Line:

      # Created by /usr/bin/gtk-query-immodules-2.0 from gtk+-2.24.33
      

      Failed to find fcitx5 in the output of /usr/bin/gtk-query-immodules-2.0

      Cannot find fcitx5 im module for gtk 2.

    2. gtk 3:

      Found gtk-query-immodules for gtk 3.24.52 at /usr/bin/gtk-query-immodules-3.0.
      Version Line:

      # Created by /usr/bin/gtk-query-immodules-3.0 from gtk+-3.24.52
      

      Found fcitx5 im modules for gtk 3.24.52.

      "/usr/lib/gtk-3.0/3.0.0/immodules/im-fcitx5.so" 
      "fcitx" "Fcitx5 (Flexible Input Method Framework5)" "fcitx5" "/usr/locale" "ja:ko:zh:*" 
      "fcitx5" "Fcitx5 (Flexible Input Method Framework5)" "fcitx5" "/usr/locale" "ja:ko:zh:*" 
      
  3. Gtk IM module cache:

    1. gtk 2:

      Found immodules cache for gtk 2.24.33 at /usr/lib/gtk-2.0/2.10.0/immodules.cache.
      Version Line:

      # Created by /usr/bin/gtk-query-immodules-2.0 from gtk+-2.24.33
      

      Failed to find fcitx5 in immodule cache at /usr/lib/gtk-2.0/2.10.0/immodules.cache

      Cannot find fcitx5 im module for gtk 2 in cache.

    2. gtk 3:

      Found immodules cache for gtk 3.24.52 at /usr/lib/gtk-3.0/3.0.0/immodules.cache.
      Version Line:

      # Created by /usr/bin/gtk-query-immodules-3.0 from gtk+-3.24.52
      

      Found fcitx5 im modules for gtk 3.24.52.

      "/usr/lib/gtk-3.0/3.0.0/immodules/im-fcitx5.so" 
      "fcitx" "Fcitx5 (Flexible Input Method Framework5)" "fcitx5" "/usr/locale" "ja:ko:zh:*" 
      "fcitx5" "Fcitx5 (Flexible Input Method Framework5)" "fcitx5" "/usr/locale" "ja:ko:zh:*" 
      
  4. Gtk IM module files:

    1. gtk 2:

      All found Gtk 2 immodule files exist.

    2. gtk 3:

      All found Gtk 3 immodule files exist.

    3. gtk 4:

      All found Gtk 4 immodule files exist.

Configuration:

Fcitx Addons:

  1. Addon Config Dir:

    Found fcitx5 addon config directory: /usr/share/fcitx5/addon.

  2. Addon List:

    1. Found 30 enabled addons:

      Simplified and Traditional Chinese Translation 5.1.13
      Classic User Interface 5.1.21
      Clipboard 5.1.21
      Cloud Pinyin 5.1.13
      DBus 5.1.21
      DBus Frontend 5.1.21
      Emoji 5.1.21
      Fcitx4 Frontend 5.1.21
      Full width character 5.1.13
      IBus Frontend 5.1.21
      Lua IME API 5.0.17
      Input method selector 5.1.21
      Keyboard 5.1.21
      KDE Input Method Panel 5.1.21
      Lua Addon Loader 5.0.17
      Status Notifier 5.1.21
      Notification 5.1.21
      Pinyin 5.1.13
      Extra Pinyin functionality 5.1.13
      Punctuation 5.1.13
      Quick Phrase 5.1.21
      Rime 5.1.14
      Spell 5.1.21
      Table 5.1.13
      Unicode 5.1.21
      DBus Virtual Keyboard 5.1.21
      Wayland 5.1.21
      Wayland Input method frontend 5.1.21
      XCB 5.1.21
      X Input Method Frontend 5.1.21
      
    2. Found 0 disabled addons:

  3. Addon Libraries:

    All libraries for all addons are found.

  4. User Interface:

    Found 3 enabled user interface addons:

    Classic User Interface
    KDE Input Method Panel
    DBus Virtual Keyboard
    

Input Methods:

  1. /home/human/.config/fcitx5/profile:

    [Groups/0]
    # Group Name
    Name=默认
    # Layout
    Default Layout=us
    # Default Input Method
    DefaultIM=shuangpin
    
    [Groups/0/Items/0]
    # Name
    Name=keyboard-us
    # Layout
    Layout=
    
    [Groups/0/Items/1]
    # Name
    Name=shuangpin
    # Layout
    Layout=
    
    [Groups/1]
    # Group Name
    Name=薄荷
    # Layout
    Default Layout=us
    # Default Input Method
    DefaultIM=rime
    
    [Groups/1/Items/0]
    # Name
    Name=keyboard-us
    # Layout
    Layout=
    
    [Groups/1/Items/1]
    # Name
    Name=rime
    # Layout
    Layout=
    
    [GroupOrder]
    0=薄荷
    1=默认
    

Log:

  1. date:

    2026年 08月 12日 星期三 12:33:43 CST
    
  2. /home/human/.config/fcitx5/crash.log:

    /home/human/.config/fcitx5/crash.log not found.

Warning: the output of fcitx5-diagnose contains sensitive information, including the distribution name, kernel version, name of currently running programs, etc.

Though such information can be helpful to developers for diagnostic purpose, please double check and remove as necessary before posting it online publicly.

证据 1:fcitx5 调试日志(失灵现场,原文摘录)

运行方式:fcitx5 --verbose "xim=5,xim_key=5,key_trace=5"

以下为失灵发生时(2026-08-11 16:15:59,本地时区)的完整日志:

D2026-08-11 16:15:59.464740 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 16:15:59.464968 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.484534 xim.cpp:122] -- XIM_SYNC_REPLY
D2026-08-11 16:15:59.484558 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.540314 xim.cpp:122] -- XIM_RESET_IC
D2026-08-11 16:15:59.540344 xim.cpp:586] XIM header opcode: 64
D2026-08-11 16:15:59.540386 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.540487 xim.cpp:122] -- XIM_RESET_IC
D2026-08-11 16:15:59.540499 xim.cpp:586] XIM header opcode: 64
D2026-08-11 16:15:59.540561 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.540725 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:15:59.540732 xim.cpp:586] XIM header opcode: 60
D2026-08-11 16:15:59.540741 xim.cpp:647] XIM Key Event: 2 s time:9577303 detail: 39 state: 16 sequence:42271
D2026-08-11 16:15:59.540748 instance.cpp:961] KeyEvent: Key(s states=0) rawKey: Key(s states=16) origKey: Key(s states=16) Release:0 keycode: 39 program: UnrealEditor
D2026-08-11 16:15:59.540851 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 16:15:59.540912 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.540916 xim.cpp:122] -- XIM_DESTROY_IC
D2026-08-11 16:15:59.540920 xim.cpp:586] XIM header opcode: 52
D2026-08-11 16:15:59.540961 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.541050 xim.cpp:122] -- XIM_SYNC_REPLY
D2026-08-11 16:15:59.541079 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.541136 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:15:59.541145 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.584900 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:15:59.584926 xim.cpp:169] XIM filtered event
D2026-08-11 16:15:59.686950 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:15:59.686980 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.346977 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.347004 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.427011 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.427041 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.486965 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.486992 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.586930 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.586955 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.727009 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.727036 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:00.846959 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:00.846990 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.086955 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.086981 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.126977 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.127008 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.206999 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.207033 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.307009 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.307037 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.386996 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.387024 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.486972 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.487004 xim.cpp:169] XIM filtered event
D2026-08-11 16:16:01.707006 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 16:16:01.707033 xim.cpp:169] XIM filtered event

分界点判读(注意每条 XIM_FORWARD_EVENT 后的输出差异):

时间 事件 判读
16:15:59.540725 XIM_FORWARD_EVENT('s' 键,result:0 最后一个被正常处理的键XIM Key Event + KeyEvent handling time: 0ms result:0 完整出现
16:15:59.540916 XIM_DESTROY_IC(op=52) IC 销毁
16:15:59.541050 XIM_SYNC_REPLY 对 's' 键回灌的确认,晚于 DESTROY_IC 约 134µs 到达(时间戳差值,可能存在计时误差)
16:15:59.541136 起 所有 XIM_FORWARD_EVENT 再无 XIM Key Event / KeyEvent handling —— 键全部进队列、未派发给引擎

之后直到日志结尾(16:16:35+),几十条 XIM_FORWARD_EVENT 全部没有处理行;期间 RESET_IC / GET_IC_VALUES / SET_IC_FOCUS / UNSET_IC_FOCUS 等协议消息仍正常处理——说明连接还活着,只是按键被永久拦在队列里

证据 2:客户端侧(shim)日志 + 时间对齐

客户端日志由自定义插桩库(xim-shim,见文末附录)产生,时间戳为相对时间(+XXXX.XXXs 表示距 shim 注入的相对秒数)。

对齐锚点(我在与AI对话时的原始信息):日志中,+8316对应2026-08-11 15:54:58.224313
→ 换算公式:墙钟 = 13:36:22.224313 + shim相对秒

交叉验证(shim 相对时间 ↔ fcitx5 墙钟,三个独立匹配点,计算误差约 1.4–1.7ms;受时间戳精度限制可能存在一定误差,不能严格保证):

shim 相对时间 换算墙钟 fcitx5 日志对应事件 计算误差
+9583.235s 16:16:05.459313 16:16:05.461010 XIM_UNSET_IC_FOCUS(op=59) 约 1.7ms
+9606.454s 16:16:28.678313 16:16:28.679916 XIM_SET_IC_FOCUS(op=58) 约 1.6ms
+9607.456s 16:16:29.680313 16:16:29.681750 XIM_FORWARD_EVENT 约 1.4ms

失灵分界点换算:fcitx5 16:15:59.540916 XIM_DESTROY_ICshim +9577.317s

shim 日志原文(用户消息中的原始片段,+9581.330s 起,即 DESTROY_IC 之后约 4 秒,全部按键 consumed=1 即被输入法吞掉):

[SHIM +9581.330s] EV KeyPress win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=2 -> consumed=1
[SHIM +9581.430s] EV KeyRelease win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=3 -> consumed=1
[SHIM +9581.830s] EV KeyPress win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=2 -> consumed=1
[SHIM +9581.930s] EV KeyRelease win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=3 -> consumed=1
[SHIM +9582.010s] EV KeyPress win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=2 -> consumed=1
[SHIM +9582.110s] EV KeyRelease win=0x2200088 keycode=0x44 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=3 -> consumed=1
[SHIM +9582.295s] EV LeaveNotify win=0x2200088
[SHIM +9582.295s] GRAB XUngrabKeyboard
[SHIM +9582.295s] EV XI2 ext=131 evtype=8(Leave)
[SHIM +9583.235s] EV FocusOut win=0x2200088 detail=3
[SHIM +9583.235s] GRAB XUngrabKeyboard
[SHIM] XimWrite op=64 len=8
[SHIM] XimRead op=65 len=12
[SHIM] XimWrite op=56 len=12
[SHIM] XimRead op=57 len=20
[SHIM] XimWrite op=59 len=8
[SHIM +9583.236s] EV type=87 win=0x381
[SHIM +9605.909s] EV EnterNotify win=0x2200088
[SHIM +9605.909s] GRAB XUngrabKeyboard
[SHIM +9605.909s] EV type=11 win=0x0
[SHIM +9605.909s] EV XI2 ext=131 evtype=7(Enter)
[SHIM +9606.454s] EV FocusIn win=0x2200088 detail=3
[SHIM +9606.454s] GRAB XUngrabKeyboard
[SHIM] XimWrite op=64 len=8
[SHIM] XimRead op=65 len=12
[SHIM] XimWrite op=56 len=12
[SHIM] XimRead op=57 len=20
[SHIM] XimWrite op=58 len=8
[SHIM +9606.454s] EV type=11 win=0x0
[SHIM +9606.454s] EV ClientMessage win=0x2200088 atom=247 WM_PROTOCOLS
[SHIM +9606.454s] EV XI2 ext=131 evtype=15(RawButtonPress)
[SHIM +9606.454s] EV XI2 ext=131 evtype=4(ButtonPress)
[SHIM +9606.455s] EV XI2 ext=131 evtype=4(ButtonPress)
[SHIM +9606.455s] EV type=85 win=0x929535
[SHIM +9606.455s] EV type=87 win=0x381
[SHIM +9606.455s] EV ClientMessage win=0x2200002 atom=405 _XIM_PROTOCOL
[SHIM +9606.556s] EV XI2 ext=131 evtype=16(RawButtonRelease)
[SHIM +9606.556s] EV XI2 ext=131 evtype=5(ButtonRelease)
[SHIM +9606.556s] EV XI2 ext=131 evtype=5(ButtonRelease)
[SHIM +9606.556s] EV type=85 win=0x929595
[SHIM +9606.557s] GRAB XUngrabKeyboard
[SHIM +9607.456s] EV KeyPress win=0x2200088 keycode=0x19 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=2 -> consumed=1
[SHIM +9607.556s] EV KeyRelease win=0x2200088 keycode=0x19 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=3 -> consumed=1
[SHIM +9609.596s] EV KeyPress win=0x2200088 keycode=0x19 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=2 -> consumed=1
[SHIM +9609.676s] EV KeyRelease win=0x2200088 keycode=0x19 state=0x10
[SHIM] XimWrite op=60 len=44
[SHIM] XFilterEvent win=0x0 type=3 -> consumed=1
[SHIM +9610.356s] EV XI2 ext=131 evtype=15(RawButtonPress)
[SHIM +9610.356s] EV XI2 ext=131 evtype=4(ButtonPress)
[SHIM +9610.356s] EV XI2 ext=131 evtype=4(ButtonPress)
[SHIM +9610.356s] EV type=85 win=0x92a472
[SHIM +9610.356s] EV XI2 ext=131 evtype=16(RawButtonRelease)
[SHIM +9610.356s] EV XI2 ext=131 evtype=5(ButtonRelease)
[SHIM +9610.356s] EV XI2 ext=131 evtype=5(ButtonRelease)
[SHIM +9610.356s] EV type=85 win=0x92a472
[SHIM +9610.396s] EV LeaveNotify win=0x2200088
[SHIM +9610.396s] GRAB XUngrabKeyboard
[SHIM +9610.396s] EV XI2 ext=131 evtype=8(Leave)
[SHIM +9610.396s] EV XI2 ext=131 evtype=15(RawButtonPress)
[SHIM +9610.396s] EV type=85 win=0x92a490
[SHIM +9610.396s] EV XI2 ext=131 evtype=16(RawButtonRelease)
[SHIM +9610.396s] EV type=85 win=0x92a490
[SHIM +9610.396s] EV EnterNotify win=0x2200088
[SHIM +9610.396s] GRAB XUngrabKeyboard
[SHIM +9610.396s] EV type=11 win=0x0
[SHIM +9610.396s] EV XI2 ext=131 evtype=7(Enter)
[SHIM +9610.416s] EV LeaveNotify win=0x2200088
[SHIM +9610.416s] GRAB XUngrabKeyboard
[SHIM +9610.416s] EV XI2 ext=131 evtype=8(Leave)
[SHIM +9610.416s] EV XI2 ext=131 evtype=15(RawButtonPress)
[SHIM +9610.416s] EV type=85 win=0x92a4a9
[SHIM +9610.416s] EV XI2 ext=131 evtype=16(RawButtonRelease)
[SHIM +9610.416s] EV type=85 win=0x92a4a9
[SHIM +9610.416s] EV EnterNotify win=0x2200088
[SHIM +9610.416s] GRAB XUngrabKeyboard
[SHIM +9610.416s] EV type=11 win=0x0
[SHIM +9610.416s] EV XI2 ext=131 evtype=7(Enter)
[SHIM +9610.616s] EV LeaveNotify win=0x2200088
[SHIM +9610.616s] GRAB XUngrabKeyboard
[SHIM +9610.616s] EV XI2 ext=131 evtype=8(Leave)
[SHIM +9613.636s] EV FocusOut win=0x2200088 detail=3
[SHIM +9613.636s] GRAB XUngrabKeyboard
[SHIM] XimWrite op=64 len=8
[SHIM] XimRead op=65 len=12
[SHIM] XimWrite op=56 len=12
[SHIM] XimRead op=57 len=20
[SHIM] XimWrite op=59 len=8
[SHIM +9613.637s] EV type=87 win=0x381
[SHIM +9615.827s] EV type=85 win=0x92b9cb
[SHIM +9615.836s] EV type=85 win=0x92b9db
[SHIM +9616.029s] EV type=85 win=0x92ba93
[SHIM +9616.029s] EV type=85 win=0x92ba97

判读

  • 键事件照常到达客户端(EV KeyPressXimWrite op=60 = 客户端照常把键转发给 IM)。
  • XFilterEvent 从此永远返回 consumed=1(IM 声称吞掉了键)→ SDL 按 XFilterEvent==True 丢弃按键 → 应用收不到任何键。
  • 鼠标事件、焦点事件、_XIM_PROTOCOL ClientMessage 均正常 → 连接与窗口都活着,只有按键被 IM 永久扣留。

证据 3:根因定位(xcb-imdkit 源码)

关键状态变量client->sync(xcb-imdkit 连接级布尔)。

正常逻辑(sync mode 开启时,xcb-imdkit 默认 im->use_sync_mode = true,见 src/imdkit.c:86):

  1. fcitx5 决定把键回灌时(src/frontend/xim/xim.cppresult==false 分支 → xcb_im_forward_event),xcb_im_forward_event_fullsrc/imdkit.c:850-853)给回灌消息带 XCB_XIM_SYNCHRONOUS flag,并置 client->sync = true
  2. client->sync 置位期间,该客户端后续发来的按键(XIM_FORWARD_EVENT不派发给引擎,而是排队_xcb_im_handle_forward_eventsrc/protocolhandler.c:729-730):
    if (client->sync) {
        _xcb_im_add_queue(im, client, ic->id, hdr, &frame, data);
    }
  3. 客户端处理完回灌键后回 XIM_SYNC_REPLY_xcb_im_handle_sync_replysrc/protocolhandler.c:830-862)清 client->sync 并排空队列(_xcb_im_process_queuesrc/imdkit.c:1052-1069)。

Bug 所在_xcb_im_handle_sync_replysrc/protocolhandler.c:842-847):

        xcb_im_input_context_t *ic = NULL;
        HASH_FIND(hh, client->input_contexts, &frame.input_context_ID,
                  sizeof(uint16_t), ic);
        if (!ic) {
            break;          // <-- IC 已销毁时在此提前退出
        }

        client->sync = false;           // 永远不会执行
        ...
        _xcb_im_process_queue(im, client);   // 永远不会执行

XIM_DESTROY_IC(IC 已从 client->input_contextsHASH_DELsrc/imdkit.c:986-1019)与"在途回灌键的 SYNC_REPLY"竞争时(本例时间戳差值约 134µs,可能存在计时误差),SYNC_REPLY 处理时 HASH_FIND 找不到 IC → breakclient->sync 永不复位 → 该客户端所有按键永久进队列。

Workaround(最粗暴)

关闭 fcitx5 XIM 前端的 sync mode(xcb-imdkit 提供 xcb_im_set_use_sync_mode 开关,默认开启)。

补丁fcitx5/src/frontend/xim/xim.cpp,基于 5.1.21,1 行):

diff --git a/src/frontend/xim/xim.cpp b/src/frontend/xim/xim.cpp
index 614230d4..a29431aa 100644
--- a/src/frontend/xim/xim.cpp
+++ b/src/frontend/xim/xim.cpp
@@ -160,7 +160,7 @@ public:
         if (::xim().checkLogLevel(LogLevel::Debug)) {
             xcb_im_set_log_handler(im_.get(), XimLogFunc);
         }
-        xcb_im_set_use_sync_mode(im_.get(), true);
+        xcb_im_set_use_sync_mode(im_.get(), false);
 
         filter_ = parent_->xcb()->call<fcitx::IXCBModule::addEventFilter>(
             name, [this](xcb_connection_t *, xcb_generic_event_t *event) {

说明:sync mode 只影响"回灌键"路径(client->sync 置位只在 xcb_im_forward_event_full 发生)。实测关闭后不影响中文输入、候选、上屏。此 workaround 已在 Arch Linux 上打包安装并长时间使用验证(由于该 bug 无法可靠复现,未做严格对照实验,验证依据为"补丁后长期使用不再出现失灵")。修改点见 xim.cpp:163

生效后的日志:

D2026-08-11 17:27:45.219795 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 17:27:45.219857 xim.cpp:169] XIM filtered event
D2026-08-11 17:27:45.459668 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 17:27:45.459697 xim.cpp:586] XIM header opcode: 60
D2026-08-11 17:27:45.459707 xim.cpp:647] XIM Key Event: 3 q time:13883223 detail: 24 state: 1040 sequence:58424
D2026-08-11 17:27:45.459722 instance.cpp:961] KeyEvent: Key(q states=0) rawKey: Key(q states=1040) origKey: Key(q states=1040) Release:1 keycode: 24 program: UnrealEditor
D2026-08-11 17:27:45.459744 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 17:27:45.459809 xim.cpp:169] XIM filtered event
D2026-08-11 17:27:45.719671 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 17:27:45.719702 xim.cpp:586] XIM header opcode: 60
D2026-08-11 17:27:45.719713 xim.cpp:647] XIM Key Event: 2 e time:13883491 detail: 26 state: 1040 sequence:58479
D2026-08-11 17:27:45.719727 instance.cpp:961] KeyEvent: Key(e states=0) rawKey: Key(e states=1040) origKey: Key(e states=1040) Release:0 keycode: 26 program: UnrealEditor
D2026-08-11 17:27:45.719813 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 17:27:45.719902 xim.cpp:169] XIM filtered event
D2026-08-11 17:27:45.919662 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-11 17:27:45.919693 xim.cpp:586] XIM header opcode: 60
D2026-08-11 17:27:45.919705 xim.cpp:647] XIM Key Event: 3 e time:13883679 detail: 26 state: 1040 sequence:58521
D2026-08-11 17:27:45.919718 instance.cpp:961] KeyEvent: Key(e states=0) rawKey: Key(e states=1040) origKey: Key(e states=1040) Release:1 keycode: 26 program: UnrealEditor
D2026-08-11 17:27:45.919742 inputcontext.cpp:342] KeyEvent handling time: 0ms result:0
D2026-08-11 17:27:45.919842 xim.cpp:169] XIM filtered event

判读

  • 每个 XIM_FORWARD_EVENT 都有完整的 XIM Key Event + KeyEvent handling time 处理行 → 键正常派发。
  • 全程-- XIM_SYNC_REPLY → sync 标志不再附加(client->sync 永不置位),队列拦截路径永不激活。
  • 修复结果:连续使用超长时间不再出现键盘失灵。

建议修复(仅供参考)

根因在 xcb-imdkit(fcitx5 的上游协议库),建议在 _xcb_im_handle_sync_reply 中:即使 SYNC_REPLY 到达时 IC 已不存在,也照常清 client->sync 并排空队列(队列排空逻辑 _xcb_im_process_queue 本身已有 if (ic) 防御,对已销毁 IC 的排队键会安全丢弃,src/imdkit.c:1061-1068)。

参考补丁+bug验证xcb-imdkit/src/protocolhandler.c):

diff --git a/src/protocolhandler.c b/src/protocolhandler.c
index 69783f9..e99c03b 100644
--- a/src/protocolhandler.c
+++ b/src/protocolhandler.c
@@ -11,6 +11,7 @@
 #include "parser.h"
 #include "uthash.h"
 #include <stdbool.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
@@ -842,15 +843,30 @@ void _xcb_im_handle_sync_reply(xcb_im_t *im, xcb_im_client_t *client,
         xcb_im_input_context_t *ic = NULL;
         HASH_FIND(hh, client->input_contexts, &frame.input_context_ID,
                   sizeof(uint16_t), ic);
+
+        /* The input context may have been destroyed by the client (e.g. a
+         * XIM_DESTROY_IC arrived just before this sync reply).  client->sync
+         * must still be cleared and the queue drained in that case; otherwise
+         * sync stays true forever, and every subsequent key from this client
+         * is queued but never dispatched (the client's keyboard freezes).
+         * This branch is exactly the race reported upstream: emit a notice so
+         * the otherwise-buggy path becomes observable when it is hit. */
         if (!ic) {
-            break;
+            fprintf(stderr,
+                    "[xcb-imdkit] XIM_SYNC_REPLY for already-destroyed IC "
+                    "(id=%u): keyboard-freeze path hit, clearing sync flag\n",
+                    frame.input_context_ID);
         }
-
         client->sync = false;
         if (im->sync) {
             im->sync = false;
 
-            if (im->callback) {
+            if (ic && im->callback) {
                 im->callback(im, client, ic, hdr, &frame, NULL, im->user_data);
             }
         }

行为变化:IC 正常存在时逻辑完全不变;IC 已销毁时,client->sync 照常复位、队列照常排空(排队键因 IC 不存在被 _xcb_im_process_queue 安全丢弃),连接上的后续按键恢复正常派发。

补丁在"IC 已销毁但 SYNC_REPLY 仍到达"的原 bug 路径上打印一行提示,用于直接观测该路径被命中。经过实际测试,能够触发该提示,说明该bug真实存在且可触发,就是键盘失效根源:

D2026-08-12 15:46:21.314773 xim.cpp:122] -- XIM_RESET_IC
D2026-08-12 15:46:21.314812 xim.cpp:586] XIM header opcode: 64
D2026-08-12 15:46:21.314854 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.314908 xim.cpp:122] -- XIM_RESET_IC
D2026-08-12 15:46:21.314914 xim.cpp:586] XIM header opcode: 64
D2026-08-12 15:46:21.314973 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.315102 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-12 15:46:21.315107 xim.cpp:586] XIM header opcode: 60
D2026-08-12 15:46:21.315237 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.315249 xim.cpp:122] -- XIM_DESTROY_IC
D2026-08-12 15:46:21.315252 xim.cpp:586] XIM header opcode: 52
D2026-08-12 15:46:21.315284 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.315297 xim.cpp:122] -- XIM_SYNC_REPLY
[xcb-imdkit] XIM_SYNC_REPLY for already-destroyed IC (id=4): keyboard-freeze path hit, clearing sync flag
D2026-08-12 15:46:21.315308 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.315351 xim.cpp:122] -- XIM_FORWARD_EVENT
D2026-08-12 15:46:21.315354 xim.cpp:586] XIM header opcode: 60
D2026-08-12 15:46:21.315415 xim.cpp:169] XIM filtered event
D2026-08-12 15:46:21.366873 xim.cpp:122] -- XIM_SYNC_REPLY
D2026-08-12 15:46:21.366912 xim.cpp:169] XIM filtered event

若上游认为 fcitx5 侧更合适,也可直接在 xim.cpp:163xcb_im_set_use_sync_mode(im_.get(), true) 改为 false(即上述 workaround,1 行)。


附录:调试工具 xim-shim(源码 + 使用方式)

xim-shim.zip

xim-shim 是调试本 issue 时使用的 libX11 插桩库:以同名库(libX11.so.6)替换真实 libX11,劫持 XIM 相关函数并在函数边界打印诊断信息,用于观察"客户端把按键交给 IM 之后发生了什么"。

目录结构(以本 issue 实测环境为例)

xim-shim 是一个目录(不是文件、不是库名),内容如下:

文件 作用
libX11.so.6 shim 库(编译产物),劫持 XIM 函数并打印诊断(29KB)
libX11real.so.6 原版 libX11.so.6 的改名副本,shim 调用的真实现(1.3MB)
shim_keyboard.c shim 源码(约 25KB)

原理

  1. UE/SDL 的 ELF 依赖(DT_NEEDED)是 libX11.so.6;动态链接器按文件名LD_LIBRARY_PATH 指定的目录中优先查找 → 加载 shim 版 libX11.so.6,而不是系统的 /usr/lib/libX11.so.6
  2. shim 自身的依赖(DT_NEEDED)是 libX11real.so.6(同目录的原版改名),shim 内 dlsym(RTLD_NEXT) 拿到真实现并透传。
  3. 因此所有 XIM 相关调用(XFilterEvent_XimWrite_XimReadXutf8LookupStringXOpenIM 等)都先经过 shim 打印,再落到真 libX11。
  4. 不用 LD_PRELOAD 的原因:SDL 内部部分 XIM 符号通过 dlsym 显式查找,LD_PRELOAD 注入的库不在其解析链上;同名替换(LD_LIBRARY_PATH)才可靠。

编译(首次使用或修改源码后)

cd xim-shim
# libX11real.so.6 = 原版 /usr/lib/libX11.so.6 改名后的副本
gcc -shared -fPIC -O2 -o libX11.so.6 shim_keyboard.c libX11real.so.6 -ldl

使用(客户端 = Unreal Engine 5.8.1,X11 模式)

LD_LIBRARY_PATH=/path/to/floder/xim-shim \
SDL_VIDEODRIVER=x11 \
./Engine/Binaries/Linux/UnrealEditor /path/to/YourProject.uproject

生效判据:UE 启动后,运行终端的 stderr 出现 [SHIM] 前缀的诊断行(如 [SHIM] XOpenIM ...、按键时 [SHIM] XFilterEvent ... -> consumed=0/1),说明 shim 已生效。

与 fcitx5 日志对齐的原理

shim 打印 XIM 协议消息的读写(XimWrite op=XX),与 fcitx5 的 xim.cpp:578 XIM header opcode: XX 一一对应;shim 用相对时间戳(+XXXX.XXXs),通过截屏记录下来换算成墙钟后即可与 fcitx5 日志逐条对照(本 issue "证据 2" 的交叉验证表即由此得出)。

如需补充更多信息,欢迎随时留言。

我保证以上issue的所有文本是在人工完整审核以及验证的条件下发布的。我承认并不了解fcitx5的具体代码,但上述机制我已经尽可能通过AI理解,并让AI标清来源,但可能还是存在疏漏,请见谅。所有补丁均已验证,绝非AI生成完全未经验证的虚假issue。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions