IME and CJK Support
Monolex provides first-class support for CJK (Chinese, Japanese, Korean) input through the atomic-term module and Monokinetics principles.Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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.