Skip to main content

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

PlatformSupportAPI
macOSFullCarbon TIS API
WindowsPlaceholderGetKeyboardLayout() (planned)
LinuxPlaceholderIBus/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