Files
qingjian/apps/linux/scripts/install.sh
T
jacklaoz 0f17aa15d5 feat(linux): IBus 前端,安装脚本按机器上的框架装对应前端 (#628)
GNOME 默认的输入法框架是 IBus:加一个 IBus 前端(纯 Rust,与 Fcitx5 插件共用同一个 Server),安装脚本按机器上有哪个框架装对应的前端(--frontend auto/fcitx5/ibus/both),可选把 Server 装成随登录启动的 systemd 用户服务。

协议类型搬进 qingjian_platform::protocol::linux,两个前端共用。GNOME Wayland 上组句中按应用快捷键时先把拼音上屏到原来的框。

合并 main 时保留 #351 的 --fcitx4(单独一支,不与 --frontend 同用)。
2026-10-23 13:43:26 +08:00

263 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# //! 用户目录安装:按这台机器上有哪个输入法框架装对应的前端(fcitx5 插件 / IBus 前端),Server 两边共用。
# //! 装哪几支由 --frontend 决定,缺省 auto = 能装的都装;只有 Fcitx4 的系统(Ubuntu 22.04)用 --fcitx4。
# //! 不碰系统目录,也不碰用户数据。
set -euo pipefail
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)
install_prefix="$HOME/.local"
build_profile=release
cmake_type=Release
sample=false
frontend=auto
frontend_given=false
fcitx4=false
enable_service=false
while (($#)); do
case "$1" in
--prefix) install_prefix=${2:?--prefix 需要路径}; shift 2 ;;
--frontend) frontend=${2:?--frontend 需要 auto/fcitx5/ibus/both}; frontend_given=true; shift 2 ;;
--fcitx4) fcitx4=true; shift ;;
--debug) build_profile=debug; cmake_type=Debug; shift ;;
--sample) sample=true; shift ;;
--enable-service) enable_service=true; shift ;;
--help)
cat <<'USAGE'
用法:install.sh [选项]
--prefix 目录 安装到哪(缺省 ~/.local)
--frontend 值 auto(缺省,能装的都装)/ fcitx5 / ibus / both
--fcitx4 只装 Fcitx4 插件(Ubuntu 22.04 这类只有 Fcitx4 的系统),不能与 --frontend 同用
--debug 装 debug 构建
--sample 只装样例词库,不要产品数据
--enable-service 顺手 systemctl --user enable --now 把 Server 挂上开机自启
USAGE
exit 0 ;;
*) echo "未知参数:$1" >&2; exit 2 ;;
esac
done
[[ "$install_prefix" = /* && "$install_prefix" != / ]] || { echo '需要非根绝对安装路径' >&2; exit 2; }
case "$frontend" in auto|fcitx5|ibus|both) ;; *) echo "--frontend 只认 auto / fcitx5 / ibus / both" >&2; exit 2 ;; esac
if $fcitx4 && $frontend_given; then
echo '--fcitx4 与 --frontend 不能同用:Fcitx4 与 Fcitx5 / IBus 装在不同的系统上' >&2
exit 2
fi
# fcitx5 那一支的插件是 C++,要编译器与 fcitx5 的开发包;缺了只能说清楚缺什么。
fcitx5_missing() {
local missing=() tool module
for tool in cmake c++ pkg-config; do
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
done
if command -v pkg-config >/dev/null 2>&1; then
for module in Fcitx5Core Fcitx5Utils Fcitx5Config nlohmann_json; do
pkg-config --exists "$module" || missing+=("$module")
done
fi
echo "${missing[*]:-}"
}
# Fcitx4 插件同样是 C++,要的是 fcitx4 与 X11 的开发包。
fcitx4_missing() {
local missing=() tool module
for tool in cmake c++ pkg-config; do
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
done
if command -v pkg-config >/dev/null 2>&1; then
for module in fcitx x11 xres nlohmann_json; do
pkg-config --exists "$module" || missing+=("$module")
done
fi
echo "${missing[*]:-}"
}
# IBus 那一支是纯 Rust,编译不缺东西;装了没用才是问题,所以看的是机器上有没有 ibus。
ibus_missing() {
command -v ibus-daemon >/dev/null 2>&1 || echo ibus-daemon
}
# 现在这个会话实际在用哪个框架。**只用来提示**,不决定装什么:
# 装完再切框架是常事,两支都装着不冲突(Server 按连接隔离会话)。
current_framework() {
local hint="${XMODIFIERS:-}${GTK_IM_MODULE:-}${QT_IM_MODULE:-}"
case "$hint" in
*fcitx*) echo fcitx5 ;;
*ibus*) echo ibus ;;
*)
# GNOME 会话里内建的就是 IBus,即使这几个变量都没设
case "${XDG_CURRENT_DESKTOP:-}" in *GNOME*) echo ibus ;; *) echo 未知 ;; esac ;;
esac
}
want_fcitx4=false
if $fcitx4; then
fcitx4_gap=$(fcitx4_missing)
if [[ -n "$fcitx4_gap" ]]; then
echo "装不了 Fcitx4 插件,缺:$fcitx4_gap" >&2
echo 'Debian / Ubuntu:sudo apt install cmake g++ pkg-config fcitx-libs-dev libx11-dev libxres-dev nlohmann-json3-dev' >&2
exit 1
fi
want_fcitx4=true
# 下面那段按 Fcitx5 / IBus 挑前端,Fcitx4 都不装
frontend=none
fi
fcitx5_gap=$(fcitx5_missing)
ibus_gap=$(ibus_missing)
in_use=$(current_framework)
want_fcitx5=false
want_ibus=false
case "$frontend" in
fcitx5) want_fcitx5=true ;;
ibus) want_ibus=true ;;
both) want_fcitx5=true; want_ibus=true ;;
auto)
[[ -z "$fcitx5_gap" ]] && want_fcitx5=true
[[ -z "$ibus_gap" ]] && want_ibus=true ;;
esac
if [[ "$want_fcitx5" == true && -n "$fcitx5_gap" ]]; then
echo "装不了 fcitx5 那一支,缺:$fcitx5_gap" >&2
echo 'Debian / Ubuntu:sudo apt install cmake g++ pkg-config libfcitx5core-dev libfcitx5utils-dev libfcitx5config-dev nlohmann-json3-dev' >&2
echo 'FreeBSD:pkg install bash cmake pkgconf python3 fcitx5 nlohmann-json rustup-init(rustup-init 装 Rust)' >&2
exit 1
fi
if [[ "$want_ibus" == true && -n "$ibus_gap" && "$frontend" != auto ]]; then
echo "装不了 IBus 那一支,缺:$ibus_gap(Debian / Ubuntu:sudo apt install ibus)" >&2
exit 1
fi
if [[ "$want_fcitx5" == false && "$want_ibus" == false && "$want_fcitx4" == false ]]; then
echo '两个框架都没找到:fcitx5 缺开发包、机器上也没有 ibus-daemon。' >&2
echo '装上其中一个再来,或用 --frontend 明确指定要装哪一支。' >&2
exit 1
fi
# auto 跳过某一支要说出来,不然「装好了」与「那一支根本没装」看起来一模一样。
if [[ "$frontend" == auto ]]; then
[[ "$want_fcitx5" == true ]] || echo "跳过 fcitx5 那一支(缺:$fcitx5_gap)"
[[ "$want_ibus" == true ]] || echo "跳过 IBus 那一支(缺:$ibus_gap)"
fi
# 最容易踩的一脚:装完一切正常,唯独正在用的那个框架什么都没装上。
if [[ "$in_use" == fcitx5 && "$want_fcitx5" == false && "$want_fcitx4" == false ]]; then
echo "注意:这个会话在用 fcitx5,但这次没装 fcitx5 那一支(缺:$fcitx5_gap)" >&2
fi
if [[ "$in_use" == ibus && "$want_ibus" == false && "$want_fcitx4" == false ]]; then
echo "注意:这个会话在用 IBus,但这次没装 IBus 那一支(缺:$ibus_gap)" >&2
fi
missing=()
for tool in cargo python3; do
command -v "$tool" >/dev/null 2>&1 || missing+=("$tool")
done
command -v pkg-config >/dev/null 2>&1 && { pkg-config --exists openssl || missing+=(openssl); }
# 路径与 crates/bamboo-slip/src/fonts/linux.rs 的清单对应,改那边时同步这里。
# 没有系统 UI 字体设置程序起不来,算缺依赖;中文与 emoji 字体缺了照样能起,只提示。
any_file() {
local path
for path in "$@"; do [[ -f "$path" ]] && return 0; done
return 1
}
any_file /usr/share/fonts/truetype/noto/NotoSans-Regular.ttf /usr/share/fonts/noto/NotoSans-Regular.ttf \
'/usr/share/fonts/google-noto-vf/NotoSans[wght].ttf' /usr/share/fonts/google-noto/NotoSans-Regular.ttf \
/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf /usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf \
/usr/local/share/fonts/noto/NotoSans-Regular.ttf /usr/local/share/fonts/dejavu/DejaVuSans.ttf \
|| missing+=("系统 UI 字体(Noto Sans 或 DejaVu)")
any_file /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc /usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc \
/usr/share/fonts/google-noto-sans-cjk-vf-fonts/NotoSansCJK-VF.ttc /usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc \
/usr/local/share/fonts/noto/NotoSansCJKsc-Regular.otf \
|| echo '提示:没找到中文字体(Noto Sans CJK),设置程序里的中文会画不出来' >&2
any_file /usr/share/fonts/truetype/noto/NotoColorEmoji.ttf /usr/share/fonts/noto/NotoColorEmoji.ttf \
/usr/share/fonts/google-noto-color-emoji-fonts/NotoColorEmoji.ttf /usr/local/share/fonts/noto/NotoColorEmoji.ttf \
|| echo '提示:没找到 emoji 字体(Noto Color Emoji),表情会画不出来' >&2
if ((${#missing[@]})); then
echo "缺少依赖:${missing[*]}" >&2
echo 'Debian / Ubuntu:sudo apt install python3 pkg-config libssl-dev fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji;Rust 见 https://rustup.rs' >&2
exit 1
fi
# FreeBSD 的 realpath 没有 -m,用 python3 解析(上面已经确认有 python3)
install_prefix=$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$install_prefix")
cargo_output=$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "${CARGO_TARGET_DIR:-$repo_root/target}")
cargo_args=(build --manifest-path "$repo_root/Cargo.toml" --target-dir "$cargo_output" -p qingjian-linux-server -p qingjian-settings --locked)
[[ "$want_ibus" != true ]] || cargo_args+=(-p qingjian-linux-ibus)
[[ "$build_profile" != release ]] || cargo_args+=(--release)
cargo "${cargo_args[@]}"
built="$cargo_output/$build_profile"
files_args=(install "$install_prefix" --root "$repo_root" --server "$built/qingjian-linux-server" --settings "$built/qingjian-settings")
[[ "$sample" != true ]] || files_args+=(--sample)
staged=$(mktemp -d)
trap 'rm -rf -- "$staged"' EXIT
if [[ "$want_fcitx5" == true ]]; then
cmake_output="$repo_root/target/fcitx5-install-$build_profile"
cmake -S "$repo_root/apps/linux/fcitx5" -B "$cmake_output" "-DCMAKE_BUILD_TYPE=$cmake_type" -DBUILD_TESTING=OFF
cmake --build "$cmake_output" --parallel "${CMAKE_BUILD_PARALLEL_LEVEL:-2}"
files_args+=(--plugin "$cmake_output/qingjian.so")
fi
if [[ "$want_fcitx4" == true ]]; then
cmake_output="$repo_root/target/fcitx4-install-$build_profile"
cmake -S "$repo_root/apps/linux/fcitx4" -B "$cmake_output" "-DCMAKE_BUILD_TYPE=$cmake_type"
cmake --build "$cmake_output" --parallel "${CMAKE_BUILD_PARALLEL_LEVEL:-2}"
files_args+=(--plugin "$cmake_output/fcitx-qingjian.so" --flavor fcitx4)
fi
if [[ "$want_ibus" == true ]]; then
# 组件 XML 里的 <exec> 是装好之后的绝对路径,所以现在就按 prefix 生成
"$built/qingjian-linux-ibus" --ibus-xml "$install_prefix/bin/qingjian-linux-ibus" > "$staged/qingjian.xml"
# **IBus 不读 ~/.local/share/ibus/component/**,只认 IBUS_COMPONENT_PATH(没设时只有系统目录),
# 所以光把 XML 放进用户目录,真会话里根本看不到这个引擎(IBus 1.5.34 上验过)。
# 用 environment.d 在会话启动时把用户目录加进去;**用户目录放最前面**:两个目录里有同名组件时
# 以排在前面的为准(也验过),装过旧版 deb 的机器上新装的才会生效。
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
# 只有 IBus 确实装在 /usr/local 下才换过去:装青简时 IBus 可能还没装,那时两处都不存在,
# 退到 /usr/local 的话之后装上的系统组件(dconf 配置程序)就不在路径里,ibus-daemon 起不来(Arch 上踩过)
system_components=/usr/share/ibus/component
[[ ! -d "$system_components" && -d /usr/local/share/ibus/component ]] && system_components=/usr/local/share/ibus/component
cat > "$staged/qingjian-ibus.conf" <<ENV
# 青简 IBus 前端(apps/linux/scripts/install.sh 生成,uninstall.sh 删除)。
# IBus 不读用户目录下的组件,只认这个变量;用户目录放最前面,同名组件以前面的为准。
IBUS_COMPONENT_PATH=$data_home/ibus/component:\${IBUS_COMPONENT_PATH:-$system_components}
ENV
files_args+=(--ibus "$built/qingjian-linux-ibus" --ibus-xml "$staged/qingjian.xml" --ibus-env "$staged/qingjian-ibus.conf")
fi
if command -v systemctl >/dev/null 2>&1; then
sed "s|@EXEC@|$install_prefix/bin/qingjian-linux-server|" \
"$repo_root/apps/linux/scripts/qingjian-server.service.in" > "$staged/qingjian-server.service"
files_args+=(--service "$staged/qingjian-server.service")
fi
python3 "$repo_root/apps/linux/scripts/files.py" "${files_args[@]}"
# ibus-daemon 的注册表缓存只看缓存里记着的目录变没变,IBUS_COMPONENT_PATH 换了它不管:
# 留着旧缓存,新加的目录(青简)可能看不到,缺了系统目录时 daemon 直接退出。删掉让它按新路径重建。
[[ "$want_ibus" != true ]] || rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/ibus/bus/registry"
echo '已安装:'
[[ "$want_fcitx5" != true ]] || echo " fcitx5 插件 $install_prefix/lib/fcitx5/qingjian.so"
[[ "$want_fcitx4" != true ]] || echo " fcitx4 插件 $install_prefix/lib/fcitx/fcitx-qingjian.so"
[[ "$want_ibus" != true ]] || echo " IBus 前端 $install_prefix/bin/qingjian-linux-ibus"
echo " Server $install_prefix/bin/qingjian-linux-server"
echo " 设置程序 $install_prefix/bin/qingjian-settings(应用菜单里的「青简设置」)"
# systemd 只从 ~/.config/systemd/user 找用户单元:配置目录被换到别处时(测试、临时安装)单元装在那里它看不见,
# 按名字 enable 反而会去开本机真装的那一份,所以这种情况不 enable、只提示
systemd_sees_unit=false
[[ "${XDG_CONFIG_HOME:-$HOME/.config}" == "$HOME/.config" ]] && systemd_sees_unit=true
if command -v systemctl >/dev/null 2>&1 && [[ "$systemd_sees_unit" == true ]]; then
if [[ "$enable_service" == true ]]; then
systemctl --user daemon-reload
systemctl --user enable --now qingjian-server.service
echo 'Server 已挂上开机自启(systemctl --user status qingjian-server)。'
else
echo "Server 现在还要手动起:$install_prefix/bin/qingjian-linux-server"
echo '挂开机自启:systemctl --user daemon-reload && systemctl --user enable --now qingjian-server.service'
fi
else
[[ "$systemd_sees_unit" == true || "$enable_service" != true ]] \
|| echo 'XDG_CONFIG_HOME 不是 ~/.config,systemd 看不到这份单元,没有 enable(避免误开本机真装的那一份)。'
echo "Server 要手动起:$install_prefix/bin/qingjian-linux-server"
fi
[[ "$want_fcitx5" != true ]] || echo 'fcitx5:重启 Fcitx5,在配置工具取消“仅显示当前语言”后添加“青简”。'
[[ "$want_fcitx4" != true ]] || echo 'fcitx4:重启 Fcitx4,在配置工具添加“青简”。'
if [[ "$want_ibus" == true ]]; then
echo 'IBus:**重新登录一次**(组件路径在会话启动时读),然后在“设置 → 键盘”里添加“青简”,或 ibus engine qingjian。'
echo ' IBus 那一支只是前端,要 Server 起着才能打字;没加 --enable-service 的话记得按上面的命令挂自启。'
if [[ -f /usr/share/ibus/component/qingjian.xml ]]; then
echo '注意:系统里还装着另一份青简(/usr/share/ibus/component/qingjian.xml,多半是旧版 deb)。'
echo ' 重新登录后以这次装的为准;想回到那一份就跑 uninstall.sh。'
fi
fi