#!/usr/bin/env bash # install-ibus-pinyin.sh — Install IBus + libpinyin and add Intelligent Pinyin # to GNOME's Input Sources. Tuned for Ubuntu 25.10 (GNOME 49 / Wayland) but # works on any modern Ubuntu/Debian GNOME desktop. # # Hardened: distro-detect, runs as the desktop user (not root) for gsettings, # uses sudo only for apt steps, idempotent re-runs, ERR trap, --dry-run. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=0 SKIP_GSETTINGS=0 usage() { cat <&2; } die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR while (( $# )); do case "$1" in --dry-run) DRY_RUN=1 ;; --skip-gsettings) SKIP_GSETTINGS=1 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac shift done # Resolve the desktop user. If invoked via sudo, gsettings must target SUDO_USER. if (( EUID == 0 )); then DESKTOP_USER="${SUDO_USER:-}" [[ -n "$DESKTOP_USER" && "$DESKTOP_USER" != "root" ]] \ || die "Run as a normal user (the script will call sudo itself for apt). gsettings can't run as root." else DESKTOP_USER="$USER" fi # Look up the user's UID for the DBus session bus. USER_ENTRY="$(getent passwd "$DESKTOP_USER")" || die "User '$DESKTOP_USER' not found in passwd." USER_ID="$(awk -F: '{print $3}' <<<"$USER_ENTRY")" [[ -r /etc/os-release ]] || die "/etc/os-release not found." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) : ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}. Requires Debian/Ubuntu." ;; esac log "Detected: ${PRETTY_NAME:-unknown}, desktop user: $DESKTOP_USER" # Helper: invoke a command as $DESKTOP_USER with a working DBus address. run_as_user() { local cmd=("$@") if (( DRY_RUN )); then printf ' DRY-RUN (as %s): %s\n' "$DESKTOP_USER" "${cmd[*]}" return 0 fi if (( EUID == 0 )); then sudo -u "$DESKTOP_USER" \ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \ XDG_RUNTIME_DIR="/run/user/$USER_ID" \ "${cmd[@]}" else "${cmd[@]}" fi } # Helper: invoke a command with sudo when the caller is non-root. sudo_run() { if (( DRY_RUN )); then printf ' DRY-RUN (sudo): %s\n' "$*" return 0 fi if (( EUID == 0 )); then "$@"; else sudo "$@"; fi } export DEBIAN_FRONTEND=noninteractive log "Installing IBus Pinyin + Simplified Chinese language packs..." sudo_run apt-get update -qq sudo_run apt-get install -y \ ibus \ ibus-libpinyin \ language-pack-zh-hans \ language-pack-gnome-zh-hans # Make sure ibus-daemon picks up the new engines for the user. if command -v ibus >/dev/null 2>&1; then log "Restarting ibus-daemon for $DESKTOP_USER..." # `ibus exit` will fail if no daemon is running — treat as non-fatal. run_as_user ibus exit >/dev/null 2>&1 || true # Start fresh in the background; -drx replaces a running daemon. if (( ! DRY_RUN )); then sudo -u "$DESKTOP_USER" \ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus" \ XDG_RUNTIME_DIR="/run/user/$USER_ID" \ sh -c 'nohup ibus-daemon -drx >/dev/null 2>&1 &' || \ warn "ibus-daemon restart returned non-zero (non-fatal)." sleep 1 fi fi if (( SKIP_GSETTINGS )); then log "Skipping GNOME input-sources update (--skip-gsettings)." log "Done. Add 'Chinese (Intelligent Pinyin)' manually under Settings → Keyboard → Input Sources." exit 0 fi # Only manage gsettings if GNOME schemas are present. if ! run_as_user gsettings list-schemas 2>/dev/null | grep -q '^org.gnome.desktop.input-sources$'; then warn "GNOME schema org.gnome.desktop.input-sources not found; skipping gsettings update." log "Done. Add 'Chinese (Intelligent Pinyin)' manually in your DE's input settings." exit 0 fi log "Adding 'Intelligent Pinyin' to GNOME Input Sources (idempotent)..." CURRENT_SOURCES="$(run_as_user gsettings get org.gnome.desktop.input-sources sources 2>/dev/null || echo '[]')" # Strip the optional "@as " type annotation gvariant sometimes prepends. CLEAN_SOURCES="${CURRENT_SOURCES#@as }" if [[ "$CLEAN_SOURCES" == *"'ibus', 'libpinyin'"* ]]; then log "Intelligent Pinyin already present in input sources — nothing to do." else if [[ -z "$CLEAN_SOURCES" || "$CLEAN_SOURCES" == "[]" || "$CLEAN_SOURCES" == "@as []" ]]; then NEW_SOURCES="[('xkb', 'us'), ('ibus', 'libpinyin')]" else # Insert the libpinyin tuple before the closing bracket of the existing list. NEW_SOURCES="${CLEAN_SOURCES%]*}, ('ibus', 'libpinyin')]" fi run_as_user gsettings set org.gnome.desktop.input-sources sources "$NEW_SOURCES" log "Added: ('ibus', 'libpinyin')" fi log "Done. Switch input methods with: Super + Space" log "If 'Chinese (Intelligent Pinyin)' doesn't appear in the top bar, log out and back in."