English | 中文
Type Chinese directly in your terminal — no ibus/fcitx/XIM required. Works on headless servers over SSH, with or without tmux.
The Rime engine is embedded behind a PTY proxy: keystrokes are intercepted, composed by librime, and candidates are rendered as an inline strip right at the cursor — the shell underneath notices nothing.
Typing shurufaceshi shows an underlined preedit plus a single-line candidate
strip (1.輸入法測試 2.輸入法 3.輸入); 你好! on the left was already committed
straight into the shell prompt.
tui-ime (proxy) ──Unix socket──► tui-ime-daemon (librime)
│ ▲
│ keystroke interception │ Unix socket
│ IME rendering │
▼ │
PTY slave ──► zsh/bash tui-ime-popup (skeleton)
(runs under a bare terminal or tmux)
- daemon: systemd user service, single instance. Owns the librime lifecycle, a session pool, and the IPC service.
- proxy: one instance per terminal session. PTY interception → CSI u parsing → daemon IPC → inline ANSI rendering.
- popup: tmux
display-popupcandidate window (skeleton).
| Layer | Tech |
|---|---|
| Keyboard protocol | Kitty keyboard protocol (built into WezTerm) |
| Candidate window | tmux display-popup |
| Input engine | librime (Rime, C API) |
| PTY management | Rust portable-pty |
| Language | Rust |
Target environment: WezTerm + zsh/bash on Linux, optionally inside tmux. Other kitty-protocol terminals (kitty / foot / Ghostty / Alacritty) can be adapted on demand. xterm / urxvt / Linux console are not supported.
# 1. Install dependencies
sudo apt install librime-dev libclang-dev rime-data-luna-pinyin
# 2. Build and install the binaries
cargo build --release
install -Dm755 target/release/tui-ime-daemon ~/.local/bin/tui-ime-daemon
install -Dm755 target/release/tui-ime ~/.local/bin/tui-ime
# 3. Install and start the daemon (systemd user service: autostart on login,
# auto-restart on crash)
install -Dm644 tui-ime-daemon.service ~/.config/systemd/user/tui-ime-daemon.service
systemctl --user daemon-reload
systemctl --user enable --now tui-ime-daemon
# 4. Wrap every interactive shell in the proxy (works with or without tmux).
# Append to the END of ~/.zshrc — nothing after `exec` will run:
cat >> ~/.zshrc <<'EOF'
# tui-ime: terminal-embedded Chinese IME (daemon managed by systemd --user)
if [[ -o interactive && -z "$TUI_IME_ACTIVE" \
&& -S "${XDG_RUNTIME_DIR:-$HOME/.local/share}/tui-ime/daemon.sock" \
&& -x "$HOME/.local/bin/tui-ime" ]]; then
exec "$HOME/.local/bin/tui-ime"
fi
EOF
# bash users: append the same block to ~/.bashrc and replace the
# interactive check with [[ $- == *i* ]]
# You can also skip the shell integration and just run `tui-ime` manually.
# Default toggle key: Ctrl+\ (does not clash with system IMEs)
# While composing, an inline preedit + candidate strip appears at the cursor.About the guards: TUI_IME_ACTIVE is injected by the proxy to prevent nested
wrapping (a shell started inside the proxy skips re-wrapping); if the socket or
the binary is missing, the shell falls back to normal operation.
When the daemon is down, the proxy silently degrades to plain passthrough (toggle does nothing). To troubleshoot:
systemctl --user status tui-ime-daemon # should be active (running)
ls /run/user/$UID/tui-ime/daemon.sock # socket should existThe default is Ctrl+\ (codepoint=92, modifiers=5 i.e. Ctrl).
To use Ctrl+Space or another key:
# Environment variable (takes effect immediately)
TUI_IME_TOGGLE=32:5 tui-ime # Ctrl+Space
TUI_IME_TOGGLE=96:5 tui-ime # Ctrl+`
# Config file (~/.config/tui-ime/tui-ime.toml)
[proxy]
toggle_codepoint = 32 # Space
toggle_modifiers = 5 # Ctrl (kitty encoding = bitmask + 1; Ctrl=5, Alt=3, Shift=2)Modifiers use the raw Kitty keyboard protocol encoding (actual modifier bitmask
- 1). Common values: none=1, Shift=2, Alt=3, Ctrl=5, Ctrl+Shift=7.
tui-ime uses the standard Rime user data directory at
~/.local/share/tui-ime/rime — anything that works for other Rime frontends
(ibus-rime, fcitx5-rime, Weasel) works here: *.custom.yaml patches, custom
schemas and dictionaries, custom_phrase.txt, and so on. Just drop the files
in and redeploy:
systemctl --user restart tui-ime-daemon # redeploys on next session
# if not picked up: rm -rf ~/.local/share/tui-ime/rime/build, then restartThere is no explicit deploy command: restarting the daemon only restarts the
process. The actual deployment is triggered lazily by librime when the first
session is created after a restart (i.e. the next time a terminal opens the
proxy) — that first connection may take a few seconds while schemas rebuild.
Deleting build/ forces a full rebuild; otherwise deployment is incremental.
Schema/dict source files shared by all frontends live in /usr/share/rime-data
(e.g. rime-data-luna-pinyin; more are available as rime-data-* packages such
as rime-data-double-pinyin). The user dictionary and learned frequencies are
stored per-installation under ~/.local/share/tui-ime/rime/, separate from
ibus/fcitx.
Note: horizontal/vertical layout and other UI-style settings are frontend
concerns — tui-ime renders its own single-line strip, so options like
style/horizontal have no effect. If the cursor line has too little room
(typing at the right edge), the strip temporarily moves to a borrowed blank
line below the cursor (ANSI IL/DL) and is restored on commit.
In a bare terminal the proxy negotiates extended keys with WezTerm directly —
no configuration needed. Inside tmux, tmux must forward and re-encode extended
keys (~/.tmux.conf):
set -s -g extended-keys on # extended keys on the pane side (server option)
set -g extended-keys-format csi-u # report in CSI u format
set -as terminal-features ",xterm-256color:extkeys" # declare outer terminal supportDetach and reattach for the change to take effect (extended-keys always can
replace the third line).
The proxy also mirrors the pane's inner foreground program for tmux: as it
changes (zsh → nvim → zsh) the proxy drives the window name via
tmux rename-window (tmux disables per-window automatic-rename once a name is
set; if you rename the window manually the proxy stops driving it), and new
windows/splits inherit the inner shell's current directory.
tui-ime/
├── README.md ← you are here
├── README_zh.md ← 中文说明
├── AGENTS.md ← AI agent conventions
├── STATUS.md ← current project status
├── tui-ime-daemon.service ← systemd user service
├── screenshot/ ← usage screenshots
├── docs/reports/ ← analysis reports (not committed)
├── thirdpart/ ← local upstream references (not committed)
└── src/
├── lib.rs ← library root
├── protocol.rs ← IPC message types
├── ipc.rs ← Unix socket transport
├── config.rs ← tui-ime.toml loading
├── daemon.rs ← session pool + dispatch
├── ime.rs ← librime session wrapper
├── keyevent.rs ← CSI u / SS3 / modifyOtherKeys parsing
├── keymap.rs ← key → rime keycode mapping
├── identity.rs ← foreground identity mirror (tmux)
├── proxy.rs ← PTY proxy core
├── render.rs ← inline ANSI candidate strip
├── main.rs ← tui-ime (proxy) entry
└── bin/
├── daemon.rs ← tui-ime-daemon entry
└── popup.rs ← tui-ime-popup entry (skeleton)
