IME and CJK Support
Monolex provides first-class support for CJK (Chinese, Japanese, Korean) input through the atomic-term module and Monokinetics principles.╔═══════════════════════════════════════════════════════════════════════════════╗
║ IME/CJK SUPPORT IN MONOLEX ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ 1. ATOMIC-TERM IME ║
║ Real-time input source detection via macOS Carbon API ║
║ Composition state handling for CJK input methods ║
║ ║
║ 2. MONOKINETICS CJK GUARANTEE ║
║ Every Asian character occupies exactly 2 cells ║
║ Enforced through VTE flags at the parser level ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
The CJK Problem in Terminals
┌───────────────────────────────────────────────────────────────────────────────┐
│ PROBLEM 1: INPUT (IME Composition) │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ User types: h -> a -> n (in Korean IME) │
│ │
│ WITHOUT proper handling: │
│ Each keystroke sent immediately to PTY -> Garbage characters │
│ │
│ WITH proper handling: │
│ Buffer until composition complete -> Only final text sent │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────┐
│ PROBLEM 2: DISPLAY (Character Width) │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ WITHOUT width guarantee: │
│ Different fonts = different widths -> ASCII art breaks │
│ │
│ WITH Monokinetics guarantee: │
│ CJK = exactly 2 cells (always) -> Perfect alignment │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Atomic-Term IME Architecture
╔═══════════════════════════════════════════════════════════════════════════════╗
║ ATOMIC-TERM IME ARCHITECTURE ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ macOS System ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ Input Source: Dubeolsik (Korean) │ ║
║ │ ID: com.apple.inputmethod.Korean.2SetKorean │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ │ ║
║ │ Carbon API (TIS) ║
║ v ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ Rust Backend (atomic_term_ime.rs) │ ║
║ │ │ ║
║ │ TISCopyCurrentKeyboardInputSource() │ ║
║ │ TISGetInputSourceProperty(source, key) │ ║
║ │ │ ║
║ │ Result: InputSourceInfo { │ ║
║ │ source_id, language, is_cjk, name, needs_composition │ ║
║ │ } │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ │ ║
║ │ Tauri IPC / Event ║
║ v ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ Frontend (ime-input-layer.ts) │ ║
║ │ │ ║
║ │ if (inputSource.is_cjk) handleCJKInput(); │ ║
║ │ else writeToTerminal(event.key); │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
IME Composition States
╔═══════════════════════════════════════════════════════════════════════════════╗
║ IME COMPOSITION STATES ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ Korean Input Example: ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ Keystroke Composing Buffer Committed State │ ║
║ │ ───────── ──────────────── ───────── ───────── │ ║
║ │ h (jamo) (first jamo) "" composing │ ║
║ │ a (syllable) "" composing │ ║
║ │ n (syllable) "" composing │ ║
║ │ Space "" "han " committed │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ Japanese Input Example: ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ n -> i -> h -> o -> n -> g -> o │ ║
║ │ romaji -> hiragana -> conversion -> kanji -> commit │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ Frontend Handling: ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ compositionstart -> Start buffering │ ║
║ │ compositionupdate -> Update preview │ ║
║ │ compositionend -> Flush to PTY │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
Input Source Change Detection
╔═══════════════════════════════════════════════════════════════════════════════╗
║ INPUT SOURCE CHANGE MONITORING ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ Notification: "com.apple.Carbon.TISNotifySelectedKeyboardInputSourceChanged"║
║ ║
║ User Switching Methods: ║
║ ┌───────────────────────────────────────────────────────────────────────┐ ║
║ │ Cmd + Space -> Switch to next input source │ ║
║ │ Caps Lock -> English/Korean toggle (if configured) │ ║
║ │ Globe Key -> Input source selection popup │ ║
║ └───────────────────────────────────────────────────────────────────────┘ ║
║ ║
║ Flow: User switches -> macOS notifies -> Monolex callback -> Frontend ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
CJK Detection Logic
┌───────────────────────────────────────────────────────────────────────────────┐
│ CJK DETECTION LOGIC │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ By language code: │
│ "ko" │ "ja" │ "zh" │ "zh-Hans" │ "zh-Hant" │
│ │
│ Or by source ID keywords: │
│ "Korean" │ "Japanese" │ "Chinese" │ "Pinyin" │ "Hiragana" │ "Katakana" │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Monokinetics CJK Guarantee
╔═══════════════════════════════════════════════════════════════════════════════╗
║ MONOKINETICS TENET 1: CJK DOUBLE-WIDTH GUARANTEE ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ "Every ASCII character is 1 cell wide. ║
║ Every CJK character is exactly 2 cells wide. ║
║ Always. Predictably." ║
║ ║
║ ┌───┬───┬───┬───┬───┬───┬───────┬───────┐ ║
║ │ H │ e │ l │ l │ o │ │ CJK-1 │ CJK-2 │ ║
║ └───┴───┴───┴───┴───┴───┴───────┴───────┘ ║
║ 1 1 1 1 1 1 2 2 ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
VTE Width Flags
┌───────────────────────────────────────────────────────────────────────────────┐
│ VTE WIDTH FLAGS │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Flag Meaning Width │
│ ─────────────────────────── ───────────────────────── ────── │
│ WIDE_CHAR CJK/emoji (2 cols) 2 │
│ WIDE_CHAR_SPACER Trailing empty cell 0 │
│ LEADING_WIDE_CHAR_SPACER Leading empty cell 0 │
│ (none) Normal ASCII/Latin 1 │
│ │
│ Decision Tree: │
│ Cell -> WIDE_CHAR_SPACER? -> width=0 (skip) │
│ -> WIDE_CHAR? -> width=2 (CJK) │
│ -> else -> Unicode width lookup (usually 1) │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Rendering Flow
┌───────────────────────────────────────────────────────────────────────────────┐
│ CJK RENDERING FLOW │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Input: CJK from IME -> VTE Parser -> Grid: [CJK│spacer│CJK│spacer] │
│ │
│ xterm.js renders 4 columns (2 visible + 2 spacers) │
│ │
│ Screen: [CJK ][CJK ] <── Each takes 2-cell space │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Frontend Usage
┌───────────────────────────────────────────────────────────────────────────────┐
│ IME INTEGRATION PATTERNS │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ PATTERN 1: Event-based (Recommended) │
│ ───────────────────────────────────── │
│ 1. invoke('atomic_term_start_ime_watcher') │
│ │
│ 2. listen('atomic-term-ime-changed', callback) │
│ │ │
│ └──> On event: │
│ info = event.payload │
│ info.is_cjk? │
│ │ │
│ YES ─┴──> enableCJKMode() │
│ NO ────> disableCJKMode() │
│ │
│ PATTERN 2: Polling │
│ ────────────────────── │
│ info = invoke('atomic_term_get_input_source') │
│ info.is_cjk? -> enableCJKMode() : disableCJKMode() │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Available Commands
atomic_term_get_input_source
Get current input source info
atomic_term_is_cjk_input
Quick CJK check (boolean)
atomic_term_start_ime_watcher
Start monitoring changes
atomic_term_stop_ime_watcher
Stop monitoring
Platform Support
| Platform | Support | API |
|---|---|---|
| macOS | Full | Carbon TIS API |
| Windows | Placeholder | GetKeyboardLayout() (planned) |
| Linux | Placeholder | IBus/Fcitx D-Bus (planned) |
Summary
╔═══════════════════════════════════════════════════════════════════════════════╗
║ IME/CJK SUPPORT SUMMARY ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ WHAT: Complete CJK input and display support ║
║ HOW: Carbon API + VTE flags + Composition events ║
║ WHY: Perfect CJK experience for Korean/Japanese/Chinese users ║
║ ║
║ Key Benefits: ║
║ * Real-time IME detection (no configuration needed) ║
║ * Proper composition handling (no garbage characters) ║
║ * Guaranteed double-width (no alignment issues) ║
║ * Works with all CJK input methods ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
Zero configuration required. Monolex automatically detects your input method and handles CJK input correctly. Just switch to your preferred keyboard and start typing.
Next Steps
Monokinetics Philosophy
Learn about the design philosophy behind CJK guarantees.
Grid System
Understand how cells and widths are managed.