博主记录在 ETX 的 XFCE 桌面中使用搜狗输入法,并从桌面终端通过 SSH 或 LSF 在远端启动 VS Code 时,让远端程序使用桌面输入法的方案。

环境与目标

示例环境如下:

  • 172.31.0.122:ETX 桌面机,运行 XFCE、Fcitx 4 和搜狗输入法。
  • 172.31.0.132:远端计算节点,在其上运行 VS Code。
  • 用户家目录由 NFS 共享,两台机器看到的是同一份家目录。
  • 桌面终端使用 tcsh 或 csh,通过 Environment Modules 加载 VS Code。

目标是在 122 的桌面上输入中文,并让 132 上显示到该桌面的 VS Code 也能使用同一个输入法。这里的远端图形显示依赖已有的 X11 显示连接和访问授权;仅传递输入法变量本身不能建立图形连接。

为什么远端不能直接使用 Fcitx 模块

在 122 上,终端和本地程序可以使用 Fcitx 的 GTK、Qt 输入法模块。SSH 到 132 后,即使将 GTK_IM_MODULE=fcitx、QT_IM_MODULE=fcitx 原样传过去,也不代表远端程序能够连接到 122 的 Fcitx:远端还需要对应客户端模块和可用的会话通信环境。

在本文环境中,远端 VS Code 使用 XIM 连接桌面的输入法:

1
2
3
GTK_IM_MODULE=xim
QT_IM_MODULE=xim
XMODIFIERS=@im=fcitx

其中 XMODIFIERS 指定输入法名称,两个 *_IM_MODULE 变量让远端程序使用 XIM。之前在该环境中已经手动验证过远端 VS Code 可以输入搜狗中文;XIM 的候选框位置仍可能偏离光标。

一、在桌面机安装搜狗输入法

在 122 上安装我制作好的、适配当前发行版的搜狗输入法 RPM,并安装其依赖的 Fcitx 4、GTK/Qt 输入法模块。本文环境使用的是基于搜狗官方 DEB 重新打包的本地 RPM;具体包名和依赖应以实际制品为准,不能直接假设 Ubuntu 的 DEB 可在 RHEL 上安装。

安装后可以检查:

1
2
rpm -qa | grep -E '^(fcitx|sogoupinyin)'
command -v fcitx

在 Fcitx 配置界面中确认已添加搜狗拼音,再测试切换输入法及中文输入。终端还应使用 UTF-8 编码;输入后出现问号时,先检查终端编码和 locale。

二、用桌面图标启动 Fcitx 终端

当前做法是通过定制桌面图标运行 ~/.local/bin/terminal-ime-selector。当用户在本机没有运行 xfce4-terminal 时,脚本弹出选择框,选择 fcitx 后启动 Fcitx 和带对应环境的终端。

如果已有终端进程,脚本会显示从进程启动环境推测的输入法类型,并提醒用户:需要切换时,先保存工作,再自行结束本机该用户的所有 XFCE Terminal,之后重新选择。该检查跨同一机器上该用户的桌面会话;运行期间发生的输入法切换也不一定反映在进程启动环境中。

脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/bin/bash
set -u
export LANG=C.utf8 LC_ALL=C.utf8

fail() { zenity --error --title='终端输入法选择' --text="$1"; exit 1; }
for cmd in zenity xfce4-terminal pgrep; do
    command -v "$cmd" >/dev/null 2>&1 || fail "缺少程序:$cmd"
done
[[ -n ${DISPLAY:-} ]] || fail '当前没有图形显示环境。'

uid=$(id -u)
mapfile -t pids < <(pgrep -u "$uid" -x xfce4-terminal || true)
if ((${#pids[@]})); then
    details=''
    for pid in "${pids[@]}"; do
        [[ -r /proc/$pid/environ ]] || { details+="PID $pid:无法读取启动环境"$'\n'; continue; }
        gtk='' xim='' display=''
        while IFS= read -r -d '' entry; do
            case "$entry" in
                DISPLAY=*) display=${entry#DISPLAY=} ;;
                GTK_IM_MODULE=*) gtk=${entry#GTK_IM_MODULE=} ;;
                XMODIFIERS=*) xim=${entry#XMODIFIERS=} ;;
            esac
        done < "/proc/$pid/environ"
        case "$gtk:$xim" in
            ibus:*|*:@im=ibus) kind=IBus ;;
            fcitx:*|*:@im=fcitx) kind=Fcitx ;;
            *) kind='未知' ;;
        esac
        details+="PID $pid:$kind(DISPLAY=${display:-未知})"$'\n'
    done
    zenity --info --width=650 --title='已有 Terminal' --text="该用户已运行以下 XFCE Terminal 进程:

$details
类型来自进程的启动环境,运行中切换输入法后可能不准确。

如需切换输入法,请先保存工作并结束所有任务;在确认没有需要保留的终端后,执行:

killall xfce4-terminal

该命令会结束本机该用户所有 XFCE Terminal,包括其他桌面的窗口及其中的任务。之后双击此图标重新选择。"
    exit 0
fi

method=$(zenity --list --title='选择输入法' --width=680 --height=300 \
    --text='请选择新 Terminal 使用的输入法:' --column='类型' --column='说明' \
    ibus 'IBus:使用系统内置输入法' \
    fcitx 'Fcitx:使用 Infra 团队定制的搜狗输入法,更好用') || exit 0
case "$method" in
    ibus) daemon=ibus-daemon ;;
    fcitx) daemon=fcitx ;;
    *) exit 0 ;;
esac
command -v "$daemon" >/dev/null 2>&1 || fail "本机未安装 $daemon。"

running=false
while IFS= read -r pid; do
    [[ -r /proc/$pid/environ ]] || continue
    proc_display=$(tr '\0' '\n' < "/proc/$pid/environ" | sed -n 's/^DISPLAY=//p')
    if [[ ${proc_display%.0} == "${DISPLAY%.0}" ]]; then running=true; break; fi
done < <(pgrep -u "$uid" -x "$daemon" || true)
if [[ $running == false ]]; then
    if [[ $method == ibus ]]; then
        ibus-daemon -d -x </dev/null >/dev/null 2>&1 || fail '无法启动 IBus。'
    else
        fcitx -d -s 0 </dev/null >/dev/null 2>&1 || fail '无法启动 Fcitx。'
    fi
fi
exec env GTK_IM_MODULE="$method" QT_IM_MODULE="$method" XMODIFIERS="@im=$method" xfce4-terminal

将脚本保存后设置执行权限:

1
chmod +x ~/.local/bin/terminal-ime-selector

桌面图标可以指向该脚本。例如,保存为 ~/Desktop/Terminal-Input-Method.desktop:

1
2
3
4
5
6
[Desktop Entry]
Type=Application
Name=Terminal Input Method
Exec=/home/wanlin.wang/.local/bin/terminal-ime-selector
Icon=utilities-terminal
Terminal=false

将 Exec 中的路径改为实际用户路径,并按 XFCE 桌面提示允许启动。脚本中的 C.utf8 必须是系统实际提供的 UTF-8 locale,可通过 locale -a 检查。

在选择 Fcitx 的终端里,可以先检查:

1
printenv GTK_IM_MODULE QT_IM_MODULE XMODIFIERS DISPLAY

预期前三个值分别为 fcitx、fcitx 和 @im=fcitx。随后切换到搜狗拼音,在终端中测试输入中文。

三、在桌面机本地启动 VS Code

在该终端中加载 VS Code 并启动:

module load VSCode/1.129.1
code

本地启动时,下面的 modulefile 别名不会因为普通本地环境而将 GTK、Qt 模块改为 XIM。VS Code 如果复用了以前启动的进程,可能继续使用旧进程的环境;验证新设置时应先保存工作并确保相关旧进程已经退出,或使用独立的用户数据目录测试。

四、通过 SSH 在远端启动 VS Code

1. 传递输入法名称

在桌面机 122 的 SSH 客户端配置 /etc/ssh/ssh_config 中配置:

1
SendEnv XMODIFIERS

在允许接收该变量的远端机器 /etc/ssh/sshd_config 中配置:

1
AcceptEnv XMODIFIERS

如有其他 SendEnv / AcceptEnv 配置,应合并检查,而不是假设只有这一项。修改服务端配置后,先执行 sshd -t 检查语法,再按站点的 SSH 服务管理方式重新加载配置;使用新的 SSH 连接验证:

1
printf 'DISPLAY=%s\nXMODIFIERS=%s\n' "$DISPLAY" "$XMODIFIERS"

在搜狗/Fcitx 会话下预期 XMODIFIERS=@im=fcitx。如果依靠 SSH X11 forwarding,需另行确认 SSH 的 X11 转发已正确配置;也可使用站点已有的、授权的远程 X11 显示方式。

2. 让 VS Code 使用 XIM

在本例的 /tools/modulefiles/OSS/VSCode/1.129.1 中,除了现有的 module-whatis 和 prepend-path,设置如下别名:

1
set-alias code {sh -c 'if test -n "$LSB_JOBID" && test -n "$GTK_IM_MODULE" && test -n "$QT_IM_MODULE" && test -n "$XMODIFIERS"; then export GTK_IM_MODULE=xim QT_IM_MODULE=xim; elif test -n "$SSH_CONNECTION"; then case "$XMODIFIERS" in @im=fcitx|@im=ibus) export GTK_IM_MODULE=xim QT_IM_MODULE=xim;; esac; fi; exec code "$@"' sh}

这个别名保留 XMODIFIERS:选择 Fcitx 时为 @im=fcitx,选择 IBus 时可以为 @im=ibus。SSH 环境下,如果 XMODIFIERS 是这两个值之一,就为 VS Code 设置 GTK 和 Qt 的 XIM 模块。其他值保持原样。

在远端 shell 中加载并启动:

module load VSCode/1.129.1
alias code
code

模块配置更新后,已有 shell 需要重新加载模块或开启新 shell。旧 VS Code 进程不会自动更新环境;必要时先保存工作、退出旧实例,再启动验证。

五、通过 LSF 在远端启动 VS Code

LSF 作业不一定通过 SSH 登录,因此不能只依赖 SSH_CONNECTION。上面的 alias 还检查:

1
2
3
4
LSB_JOBID
GTK_IM_MODULE
QT_IM_MODULE
XMODIFIERS

四个变量都非空时,将启动的 VS Code 的 GTK_IM_MODULE 和 QT_IM_MODULE 改为 xim,保留 XMODIFIERS。即使作业环境同时存在 SSH_CONNECTION,符合上述条件时也优先走 LSF 分支。

需要注意:

  1. LSF 是否传递这些变量取决于提交方式和站点配置。应在执行节点的作业环境里检查它们,也要确认 DISPLAY、X11 授权和连接可用。
  2. alias 是 shell 功能;必须在执行节点上真正执行 code 的 shell中加载 modulefile。提交 shell 已有 alias,不代表非交互批处理 shell 会自动拥有它。
  3. 如果这四个变量不齐,LSF 分支不会生效;若同时存在 SSH_CONNECTION,仍可能按 SSH 分支判断。
  4. 此处仅判断 LSF 的 LSB_JOBID,没有包含 Slurm 的 SLURM_JOB_ID。

当前已用模拟环境验证本地、SSH 和 LSF 三种 alias 分支及参数传递;真实 LSF 图形作业仍需按站点配置验证。

六、为什么不直接在 .cshrc 设置桌面输入法

通常启动顺序是:

1
XFCE 会话 → xfce4-terminal → tcsh/csh → 读取 ~/.cshrc

.cshrc 设置的变量对 shell 及其后启动的子进程有效,但不能修改已启动的 XFCE 会话或 xfce4-terminal server 的环境。因此,它不能可靠地决定终端窗口自身使用哪个输入法模块。

另外,家目录在 122 和 132 通过 NFS 共享。无条件在 .cshrc 设置 GTK_IM_MODULE=fcitx 等变量,会在远端 shell 也生效,可能覆盖传递过来的选择,或使远端程序尝试使用不可用的 Fcitx 客户端模块。

七、管理员统一配置:桌面启动后默认使用搜狗输入法

章节二通过桌面图标让用户手动选择并启动 Fcitx 终端。 下面介绍另一种方式:管理员配置 122 和 ETX 的 Xstart,用户启动新桌面后,直接右键 Open Terminal Here,终端就继承桌面的 Fcitx 环境,不需要运行选择图标。

本方案针对本文的 RHEL 8.10、XFCE 和 Fcitx 4 环境。在 122 上已配置系统默认项和 Fcitx 自动启动;Xstart 配置需要在新 ETX 会话中验证。

1. 安装 Fcitx 与搜狗输入法

先在 122 安装适配系统的搜狗输入法 RPM 及其 Fcitx 依赖,并确认本机有 GTK 和 Qt 输入法模块:

1
2
rpm -q fcitx fcitx-gtk2 fcitx-gtk3 fcitx-qt5
rpm -qa | grep -E '^(fcitx|sogoupinyin)'

在 Fcitx 中确认已添加搜狗拼音。默认使用 Fcitx 并不等于自动选中了搜狗输入法引擎;输入法列表和切换快捷键仍需按实际用户配置验证。

2. 将系统默认输入法设为 Fcitx

先记录当前设置,以便回退:

1
2
alternatives --display xinputrc
readlink -f /etc/X11/xinit/xinputrc

如果已安装 Fcitx 配置文件,管理员可选择它:

1
2
sudo alternatives --set xinputrc /etc/X11/xinit/xinput.d/fcitx.conf
readlink -f /etc/X11/xinit/xinputrc

预期输出为 /etc/X11/xinit/xinput.d/fcitx.conf。系统路径 /etc/X11/xinit/xinputrc 是由 alternatives 管理的符号链接;不要直接编辑链接指向的文件来切换默认项。

需要区分系统默认值与强制所有用户:~/.config/imsettings/xinputrc 可能优先于系统默认值。本机 50-xinput.sh 还根据桌面类型和 locale 判断是否加载系统配置;ETX 自定义 Xstart 也可能不经过该初始化流程。若要对所有用户执行统一策略,管理员应先盘点并处理用户覆盖及其他输入法启动入口,特别是 NFS 家目录会被其他机器共享。

3. 预置 Fcitx 的 XFCE 自动启动项

创建 /etc/xdg/autostart/infra-fcitx.desktop:

1
2
3
4
5
6
[Desktop Entry]
Type=Application
Name=Fcitx Input Method
Exec=/usr/bin/fcitx -D -s 0
OnlyShowIn=XFCE;
Terminal=false

该文件由 root 管理,通常权限为 0644。-D 让 Fcitx 保持前台进程,-s 0 覆盖延迟启动。此项只在遵循 XDG autostart 的 XFCE 会话中生效;也应检查用户级自动启动覆盖,以及是否已有其他入口启动 Fcitx。

本例为避免 XFCE 会话中的输入法管理器自动启动入口竞争,在备份 /etc/xdg/autostart/imsettings-start.desktop 后,为该文件的 [Desktop Entry] 添加:

1
NotShowIn=XFCE;

这是针对本机的试点配置,修改包管理的文件可能在升级时被覆盖;添加前先检查文件是否已有 OnlyShowIn 或 NotShowIn,并评估其他桌面和站点策略。更改不会停止已经运行的输入法。无需卸载 IBus 才能采用 Fcitx;若要求完全禁用 IBus,仍需分别检查其他启动项、用户配置和正在运行的会话。

4. 在 ETX Xstart 中设置桌面环境

本例的 ETX Xfce4 profile 使用自定义 Xstart,它执行 env -i 清空环境后直接运行 /usr/bin/xfce4-session。因此,变量必须在 env -i 后、XFCE 启动前设置;只修改 /usr/share/xsessions/xfce.desktop 对这个 profile 无效。

在 ETX 管理界面的目标 profile 中,编辑 Startup → Xstart 的 Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

env -i /bin/sh -c "export PATH=$PATH;
                   export XAUTHORITY=$XAUTHORITY;
                   export DISPLAY=$DISPLAY;
                   export HOME=$HOME;
                   export LOGNAME=$LOGNAME;
                   export USER=$USER;
                   if test -f /etc/xdg/autostart/infra-fcitx.desktop; then
                       export GTK_IM_MODULE=fcitx;
                       export QT_IM_MODULE=fcitx;
                       export XMODIFIERS=@@im=fcitx;
                   fi;
                   /usr/bin/xfce4-session"

@@ 是 Xstart 命令中的写法;预期经过宏处理后,Linux 进程获得的是 XMODIFIERS=@im=fcitx。在普通 shell 里手动设置变量时使用单个 @。Xstart 命令框支持宏;相关 Exceed Xstart 文档将 @@ 描述为插入一个 @,实际 ETX 版本仍应通过启动日志和进程环境验证。参见 Exceed TurboX Xstart 设置 和 Exceed Xstart 宏说明。

这里用自动启动文件是否存在作为节点启用标记。如果该 group profile 可运行在多个节点,任何具有此文件的节点都会应用 Fcitx 环境;其他节点不进入该分支。这不是对节点身份或输入法可用性的完整检查,应由管理员确保文件和软件一起部署。更改 group profile 会影响其用户的新会话,应先进行试点。

5. 新建 ETX 会话并验证

关闭或保留旧会话取决于当前任务;配置只对新启动的桌面进程生效。启动新的 ETX session,直接从桌面右键打开终端:

1
2
printenv DISPLAY GTK_IM_MODULE QT_IM_MODULE XMODIFIERS
pgrep -a -u "$(id -u)" -x fcitx

后三个输入法变量预期依次是 fcitx、fcitx、@im=fcitx。在终端中切换到搜狗,输入并提交中文,再依次测试本地 VS Code、SSH 到远端后的 VS Code、以及站点实际 LSF 图形作业。

xfce4-terminal 可能复用已运行的 server;如果结果不符合预期,检查终端 server 所属桌面及启动环境,避免将旧进程当成新配置的结果。不要强行结束仍有任务的终端。还应确保终端和 locale 使用可用的 UTF-8 编码。

6. 备份与回退

在本文 122 的试点中,系统修改前的备份位于 /var/lib/etx-fcitx-backup-20261030:包含原 xfce.desktop、原 imsettings-start.desktop 以及 alternatives 状态记录。ETX profile 的修改通过管理界面保存,不包含在该目录,应另行记录或导出原 profile。

回退时,恢复各个原文件,移除本次新增的自动启动项,并恢复 alternatives 原状态;例如原状态是自动选择时:

1
sudo alternatives --auto xinputrc

如果原状态为手动选择,应以 alternatives --set 恢复记录的原目标。在 ETX 管理界面恢复原 Xstart 命令;随后新建桌面会话验证。恢复包管理文件前应确认其在备份后是否被其他管理员修改。