Terminal External Input Method Issue
问题
这是我曾经出现过的两次相同的问题
第一次是在我把Ubuntu自带的DE, DM等完整图形化框架拆完了,只保留了X11并使用i3-wm作为窗口管理器时出现的
第二次是我Ubuntu换Debian过程中出现的,同样是没有完整的图形界面
具体表现为输入法只在Terminal里工作,其他的窗口不工作,包括vim等这种在终端窗口启动的应用\
分析
第一次遇到这个问题时,我怀疑过输入法框架,我换了Fcitx5, IBus;我怀疑过浏览器,我尝试用过chrome, firefox, w3m, qutebrowser等各种浏览器,甚至因为qutebrowser用的chrome内核过于老旧尝试过编译最新版本的内核,但没有结果
我一度打算接受就在Linux上使用英文环境,我甚至还写了一段时间的英文博客
我在Google和各大论坛上搜索过相关内容,无外乎是以下三种处理方式:
- 环境变量,在
~/.xprofile中写入输入法环境变量export GTK_IM_MODULE=fcitx,export QT_IM_MODULE=fcitx,export XMODIFIERS=@im=fcitx - 在i3 config里添加
exec --no-startup-id fcitx5 & - 排查GUI框架的兼容性,从古老的XIM协议到现代使用的GTK3/4, Qt5/6和Electron
- 排查Fcitx5本身的问题,用
fcitx5-diagnose进行检查
现代输入法框架(Fcitx5, IBus)已经不是键盘映射工具,本质上是C/S(客户端/服务端)架构的复杂系统
- 服务端:输入法守护进程
- 客户端:各个GUI应用程序(浏览器,编辑器等)
值得说的一点是,fcitx5-diagnose会直接检测出D-Bus的连接状态、环境变量在各个层面的存在情况
所以,经过以上的排除,问题定格在通信上,在Linux中就是D-Bus(具体来说是其中的Session Bus),它是Linux中的一个重要的IPC和RPC系统,是连接客户端和服务端的通路
如果图形界面初始化不完整,导致D-Bus没启动,或者环境变量没传递到位,总线就断了,应用窗口就无法唤起输入法
完整桌面环境的情况
完整的桌面环境(如GNOME/KDE)在启动时,通过显示管理器(如GDM/SDDM)登录DE时,会经历以下启动流程
- 拉起总线:显示管理器会话管理器(如
gnome-session)会首先调用dbus-run-session通过systemd --user启动属于你的Session Bus - 将生成的
DBUS_SESSION_BUS_ADDRESS写入当前图形绘画的根环境中 - 环境同步:通过执行
dbus-update-activation-environment --all,把这个变量同步给systemd --user空间和X11/Wayland服务端
随后由桌面拉起来的所有组件(窗口管理器、状态栏、输入法、浏览器)作为子进程,天然继承了这个环境变量,它们之间能够通信
不完整的桌面环境的情况
我只装了一个窗口管理器,并通过startx启动,就没有上述完整的流水线,总线的问题会表现为以下两种
- 直接在
.xinitrc里exec i3,整个X11里没有Session Bus进程。输入法启动时试图去注册自己,直接报错退出;应用程序想去找输入法,没有总线可以连接 - 总线启动了,但是断连,现代Linux中,即使只用
startx,systemd --user往往也会在后台自动塞一个dbus-daemon启动,但是没人把它的地址告诉X11或窗口管理器,结果就是有总线但是没人知道没人用
为什么Terminal里可以正常工作
我记不太清我当初怎么配置Ubuntu了,但是有一下三种方式会导致这种奇观
1. 在终端配置文件如.bashrc里写入fcitx5 &
这意味着fcitx5是当前终端拉起来的守护进程,当前终端自然能用
但这也会导致每做一次会执行.bashrc的操作,比如新建终端,新开标签页,在tmux里切一个新panel,都会试图拉起一个新的fcitx5,好在Fcitx5内部有单例锁
这是极其不正确的做法,让终端的配置变得很脏
2. XIM的自动回退
由于D-Bus故障,现代浏览器和终端在面对这个情况时,可能会采取完全不同的态度
- 许多终端内部保留了对XIM这个老输入法协议的支持。当D-Bus挂了,或者现代IM模块通信失效时,终端会自动向下兼容
- 现代浏览器大多为了保障安全都已经砍掉XIM,只认基于D-Bus的现代输入法框架
这就造成,当D-Bus损坏时,终端可以通过XIM进行对输入法的通信,而不认识XIM的实例无法通信
3. startx与~/.bash_profile/~/.profile的时间差切片
如果不用GDM/LightDM这种图形登录界面,而是在tty里输入密码登录,然后手动startx启动X11,这期间有一个巨大的环境变量陷阱
- 登录tty时,系统会加载
~/.bash_profile/~/.profile - 如果把输入法环境变量写在
~/.profile里,此时当前tty确实拥有了这些变量 - 接着运行
startx,如果~/。xinitrc没有显式地继承或传递当前shell的环境变量,X服务拉起来的WM拿到的就是一个纯净的环境 - 当通过WM打开一个终端时,这个终端为了初始化,可能会再次作为登录shell运行,或者重新读取了某些包含变量的配置文件
这也就造成了WM拉起来的非终端窗口不知道输入法变量;终端悄悄补齐了这些变量
解决
本质:在图形界面的“最上游”把D-Bus会话拉起来,并将输入法环境变量强行注入到整个图形世界的“根环境”中
方案1: .xinitrc或.xprofile包裹
如果是通过startx启动极简窗口管理器,或者使用LightDM这种会读取~/.xprofile的登录管理器
可以在~/.xinitrc或~/.xprofile里这么写
#!/bin/sh
# 声明输入法环境变量(让整个图形界面的父进程都拥有这些变量)
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"
# 将这些变量强行同步给 D-Bus 和 systemd 空间
# 这样通过 systemd 或是 D-Bus 激活的沙盒应用(如 Flatpak/Snap)也能拿到变量
if which dbus-update-activation-environment >/dev/null 2>&1; then
dbus-update-activation-environment --all
fi
# 后台启动输入法守护进程
fcitx5 -d &
# 用 dbus-run-session 启动窗口管理器(以 i3 为例)
# 这保证了 WM 以及它以后拉起的所有浏览器、编辑器,全部处于同一个 D-Bus 会话总线中
exec dbus-run-session i3
方案2:编辑/etc/environment
在文件末尾加入以下内容
GTK_IM_MODULE=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx
这个文件在系统刚开机、用户还没登录时就会被内核级进程读取。所有登录后的会话都会无条件继承这里面的变量
配置后需要重启生效,需要手动启动输入法
方案3:通过systemd管理
在使用systemd的发行版上,可以将fcitx5的生命周期交给systemd --usr来管理
启动Fcitx5的自带用户服务
systemctl --user enable --now fcitx5
解决systemd 空间的变量隔离,因为systemd用户实例和X11图形会话是平行的,它默认拿不到图形界面里的变量
所以必须在窗口管理器中先执行dbus-update-avtivation-environment --all
这样,每次窗口管理器起来后,会立刻把当前的D-Bus地址和输入法变量“反哺”给systemd,systemd随后拉起来的fcitx5就能和整个桌面通信
本篇主要基于X11分析,在Wayland下,情况会更复杂,因为Wayland协议要求输入法通过
zwp_input_method_v2这类专用协议进行进程间通信,对安全隔离要求更高,直接设置GTK_IM_MODULE这种变量的方式可能会失效