
High-performance GPU-accelerated terminal emulator engine with ANSI/VT100 parser, TrueColor, OSC52 clipboard policies, multi-line selection, touch/gesture support, low-allocation renderer and type-safe configuration.
A modern Kotlin Multiplatform (KMP) terminal emulator engine and Compose Multiplatform rendering library. This library provides a standalone, high-performance, GPU-accelerated terminal emulator designed for mobile, desktop, and web applications.
👉 🚀 Try Live Interactive WebAssembly Demo on GitHub Pages
The repository includes multiple demo implementations targeting different environments and runtimes:
demo-web
wasmJs) and rendered with Compose Multiplatform on a GPU-accelerated Skia canvas.main via GitHub Actions.htop preview), and configurable cursor styling.demo-jvm) — Native JVM application (macOS, Linux, Windows)demo-jvm
mise run run-jvm
# or
./gradlew :demo-jvm:rundemo-vite) — Frontend bundler integrationdemo-vite
BLOCK, BEAM, UNDERLINE), font size scaling, and one-click ANSI stress-testing scripts (color matrices, progress bars, matrix rain, htop alt-buffer).cd demo-vite
npm install
npm run devdemo-html) — Direct DOM mounting over static HTTPdemo-html
npx http-server demo-html -p 8083
# or
python3 -m http.server -d demo-html 8083npm-package) — Pre-packaged NPM bundlenpm-package
@JohNan/kmp-terminal) generated via ./build-npm.sh that bundles the production WebAssembly executable, JavaScript interop loader (demo-web.js), and Compose web resources for integration in web applications.demo-mobile) — Native Compose Multiplatform Mobile Appdemo-mobile
ComposeUIViewController export).mise run assemble-android
# or
./gradlew :demo-mobile:assembleDebugmise run compile-ios-demo
# or
./gradlew :demo-mobile:compileKotlinIosSimulatorArm64kmp-terminal powers the terminal rendering canvas and ANSI state machine in production:
A secure, high-performance Android SSH & SFTP client with hardware-backed encryption, multi-session management, and custom quick key bars.
:terminal-core for ANSI/VT100 parsing, TrueColor support, and circular scrollback buffer management.:terminal-ui for low-allocation Compose Multiplatform canvas rendering, touch gestures, and responsive IME soft-keyboard input capture.:terminal-core: Pure Kotlin multiplatform terminal state machine, screen buffer, ANSI/VT100 parser, and text selection models. Zero UI dependencies.:terminal-ui: Hardware-accelerated Compose Multiplatform canvas renderer, gesture recognizers, platform-native input capture, selection overlays, and toolbar key bars.TerminalConfig & terminalConfig { ... } DSL for core engine dimensions, scrollback capacity, OSC 52 security policies, tab stops, and bell behavior.TerminalUiConfig & terminalUiConfig { ... } DSL for typography, cursor styles (BLOCK, UNDERLINE, BEAM), gesture controls, key bar layouts, and color themes.LocalTerminalUiConfig.screenState StateFlow emitting immutable buffer snapshots on change.attentionEvents (OSC 9 / OSC 777 desktop notifications), bellEvents / bellCount, clipboardEvents / clipboardWriteCount, and windowTitle.altBuffer for vim/htop/less).ASK, ALWAYS_ALLOW, ALWAYS_DENY).TerminalEmulator.copy()) for headless sessions and speculative execution (e.g. Mosh).ScreenBuffer.isRowWrapped(row)).touchScrollSendsWheelOnly) to send mouse wheel events on mobile drag gestures in tmux and vim.wasmJs)jvm)androidMain)iosArm64, iosSimulatorArm64)kmp-terminal/
├── terminal-core/ # Pure Kotlin Multiplatform emulation engine & screen buffer (Zero UI dependencies)
├── terminal-ui/ # Compose Multiplatform canvas renderer & UI components
├── demo-mobile/ # Android & iOS Compose Multiplatform mobile showcase
├── demo-web/ # WebAssembly (wasmJs) interactive demo & JS bridge (deployed to GitHub Pages)
├── demo-jvm/ # JVM Desktop Compose demo (macOS, Linux, Windows)
├── demo-vite/ # Vite + TypeScript web application integrating Wasm terminal
├── demo-html/ # Vanilla HTML/JS zero-framework integration demo
└── npm-package/ # NPM package bundle (@JohNan/kmp-terminal)
Add the dependencies to your multiplatform build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
// Core ANSI engine, scrollback buffer, and text selection (zero UI dependencies)
implementation("io.github.johnan:terminal-core:0.3.11")
// Optional: Compose Multiplatform canvas renderer & gestures
implementation("io.github.johnan:terminal-ui:0.3.11")
}
}
}Create a TerminalEmulator instance with default or DSL-configured parameters:
import com.johnan.terminal.core.BellBehavior
import com.johnan.terminal.core.Osc52Policy
import com.johnan.terminal.core.TerminalEmulator
import com.johnan.terminal.core.terminalConfig
// DSL configuration
val config = terminalConfig {
initialRows = 24
initialCols = 80
maxScrollback = 2000
osc52Policy = Osc52Policy.ALWAYS_ALLOW
bellBehavior = BellBehavior.VISUAL
}
val emulator = TerminalEmulator(
config = config,
onTerminalResponse = { response ->
// Send terminal response back to PTY / SSH stream (e.g. CPR, DA1, mouse events)
}
)
// Process incoming ANSI byte streams
emulator.processOutput("Hello \u001B[32mGreen\u001B[0m World!\r\n")Render the terminal screen state using TerminalRenderer:
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import com.johnan.terminal.core.TerminalColorScheme
import com.johnan.terminal.ui.TerminalCursorStyle
import com.johnan.terminal.ui.TerminalRenderer
import com.johnan.terminal.ui.terminalUiConfig
@Composable
fun TerminalScreen(emulator: TerminalEmulator) {
val terminalState by emulator.screenState.collectAsState()
val uiConfig = terminalUiConfig {
typography {
fontSize = 15.sp
letterSpacing = 0.sp
}
cursor {
style = TerminalCursorStyle.BEAM // BLOCK, UNDERLINE, or BEAM
blink = true
overrideColor = Color(0xFF00FFCC)
}
gestures {
enableTouchToFocus = true
enableSelection = true
wheelScrollMultiplier = 1.5f
touchScrollSendsWheelOnly = true // Opt-in wheel-only touch scrolling for tmux/vim
}
}
TerminalRenderer(
terminalState = terminalState,
config = uiConfig,
onInput = { input ->
// Forward user keyboard input to PTY / SSH channel
},
onArrowKey = { arrow, shift ->
// Forward arrow navigation
},
onLog = { msg -> println("[TerminalLog] $msg") },
modifier = Modifier.fillMaxSize()
)
}Text selection is managed through TerminalSelection and SelectionState:
import com.johnan.terminal.core.TerminalSelection
// Programmatically extract text from a selection
val selection = TerminalSelection(startRow = 0, startCol = 0, endRow = 2, endCol = 40)
val selectedText = selection.extractText(screenBuffer)
// Selection containment check
val isInside = selection.contains(row = 1, col = 10)copy() returns a fully independent deep copy, including a partially parsed escape or UTF-8 sequence. This lets you
keep one headless emulator per received state (for example Mosh) and apply a diff to a copy of the reference state.
bellCount, clipboardWriteCount and lastClipboardWrite let you diff event-only state between snapshots, and
ScreenBuffer.isRowWrapped(row) exposes the soft-wrap flag.
val reference = TerminalEmulator(TerminalConfig(initialRows = 24, initialCols = 80))
reference.processOutput("hello")
val next = reference.copy() // callbacks are not inherited; pass them if needed
next.processOutput("\u001B[31m world") // diff applied to the copy only
val bellRang = next.bellCount > reference.bellCount
val clipboardChanged = next.clipboardWriteCount > reference.clipboardWriteCount
val firstRowWraps = next.getScreenBuffer().isRowWrapped(0)This project uses mise for reproducible toolchain management (JDK 21, Android SDK, Gradle, Node 20).
# Compile WebAssembly target
mise run compile-wasm
# Run local WebAssembly browser demo
mise run run-demo
# Run native JVM Desktop demo
mise run run-jvm
# Assemble Android debug APK (demo-mobile)
mise run assemble-android
# Compile iOS simulator target (demo-mobile)
mise run compile-ios-demo
# Build all targets
mise run build
# Execute all multiplatform unit tests
mise exec -- ./gradlew allTests
# Run fast unit test suite
mise exec -- ./gradlew test
# Verify Kotlin code formatting and linting
mise exec -- ./gradlew ktlintCheckContributions are welcome! Please read through our community guides:
Distributed under the Apache License, Version 2.0. Free for commercial and private use with attribution required and express patent grant.
A modern Kotlin Multiplatform (KMP) terminal emulator engine and Compose Multiplatform rendering library. This library provides a standalone, high-performance, GPU-accelerated terminal emulator designed for mobile, desktop, and web applications.
👉 🚀 Try Live Interactive WebAssembly Demo on GitHub Pages
The repository includes multiple demo implementations targeting different environments and runtimes:
demo-web
wasmJs) and rendered with Compose Multiplatform on a GPU-accelerated Skia canvas.main via GitHub Actions.htop preview), and configurable cursor styling.demo-jvm) — Native JVM application (macOS, Linux, Windows)demo-jvm
mise run run-jvm
# or
./gradlew :demo-jvm:rundemo-vite) — Frontend bundler integrationdemo-vite
BLOCK, BEAM, UNDERLINE), font size scaling, and one-click ANSI stress-testing scripts (color matrices, progress bars, matrix rain, htop alt-buffer).cd demo-vite
npm install
npm run devdemo-html) — Direct DOM mounting over static HTTPdemo-html
npx http-server demo-html -p 8083
# or
python3 -m http.server -d demo-html 8083npm-package) — Pre-packaged NPM bundlenpm-package
@JohNan/kmp-terminal) generated via ./build-npm.sh that bundles the production WebAssembly executable, JavaScript interop loader (demo-web.js), and Compose web resources for integration in web applications.demo-mobile) — Native Compose Multiplatform Mobile Appdemo-mobile
ComposeUIViewController export).mise run assemble-android
# or
./gradlew :demo-mobile:assembleDebugmise run compile-ios-demo
# or
./gradlew :demo-mobile:compileKotlinIosSimulatorArm64kmp-terminal powers the terminal rendering canvas and ANSI state machine in production:
A secure, high-performance Android SSH & SFTP client with hardware-backed encryption, multi-session management, and custom quick key bars.
:terminal-core for ANSI/VT100 parsing, TrueColor support, and circular scrollback buffer management.:terminal-ui for low-allocation Compose Multiplatform canvas rendering, touch gestures, and responsive IME soft-keyboard input capture.:terminal-core: Pure Kotlin multiplatform terminal state machine, screen buffer, ANSI/VT100 parser, and text selection models. Zero UI dependencies.:terminal-ui: Hardware-accelerated Compose Multiplatform canvas renderer, gesture recognizers, platform-native input capture, selection overlays, and toolbar key bars.TerminalConfig & terminalConfig { ... } DSL for core engine dimensions, scrollback capacity, OSC 52 security policies, tab stops, and bell behavior.TerminalUiConfig & terminalUiConfig { ... } DSL for typography, cursor styles (BLOCK, UNDERLINE, BEAM), gesture controls, key bar layouts, and color themes.LocalTerminalUiConfig.screenState StateFlow emitting immutable buffer snapshots on change.attentionEvents (OSC 9 / OSC 777 desktop notifications), bellEvents / bellCount, clipboardEvents / clipboardWriteCount, and windowTitle.altBuffer for vim/htop/less).ASK, ALWAYS_ALLOW, ALWAYS_DENY).TerminalEmulator.copy()) for headless sessions and speculative execution (e.g. Mosh).ScreenBuffer.isRowWrapped(row)).touchScrollSendsWheelOnly) to send mouse wheel events on mobile drag gestures in tmux and vim.wasmJs)jvm)androidMain)iosArm64, iosSimulatorArm64)kmp-terminal/
├── terminal-core/ # Pure Kotlin Multiplatform emulation engine & screen buffer (Zero UI dependencies)
├── terminal-ui/ # Compose Multiplatform canvas renderer & UI components
├── demo-mobile/ # Android & iOS Compose Multiplatform mobile showcase
├── demo-web/ # WebAssembly (wasmJs) interactive demo & JS bridge (deployed to GitHub Pages)
├── demo-jvm/ # JVM Desktop Compose demo (macOS, Linux, Windows)
├── demo-vite/ # Vite + TypeScript web application integrating Wasm terminal
├── demo-html/ # Vanilla HTML/JS zero-framework integration demo
└── npm-package/ # NPM package bundle (@JohNan/kmp-terminal)
Add the dependencies to your multiplatform build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
// Core ANSI engine, scrollback buffer, and text selection (zero UI dependencies)
implementation("io.github.johnan:terminal-core:0.3.11")
// Optional: Compose Multiplatform canvas renderer & gestures
implementation("io.github.johnan:terminal-ui:0.3.11")
}
}
}Create a TerminalEmulator instance with default or DSL-configured parameters:
import com.johnan.terminal.core.BellBehavior
import com.johnan.terminal.core.Osc52Policy
import com.johnan.terminal.core.TerminalEmulator
import com.johnan.terminal.core.terminalConfig
// DSL configuration
val config = terminalConfig {
initialRows = 24
initialCols = 80
maxScrollback = 2000
osc52Policy = Osc52Policy.ALWAYS_ALLOW
bellBehavior = BellBehavior.VISUAL
}
val emulator = TerminalEmulator(
config = config,
onTerminalResponse = { response ->
// Send terminal response back to PTY / SSH stream (e.g. CPR, DA1, mouse events)
}
)
// Process incoming ANSI byte streams
emulator.processOutput("Hello \u001B[32mGreen\u001B[0m World!\r\n")Render the terminal screen state using TerminalRenderer:
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import com.johnan.terminal.core.TerminalColorScheme
import com.johnan.terminal.ui.TerminalCursorStyle
import com.johnan.terminal.ui.TerminalRenderer
import com.johnan.terminal.ui.terminalUiConfig
@Composable
fun TerminalScreen(emulator: TerminalEmulator) {
val terminalState by emulator.screenState.collectAsState()
val uiConfig = terminalUiConfig {
typography {
fontSize = 15.sp
letterSpacing = 0.sp
}
cursor {
style = TerminalCursorStyle.BEAM // BLOCK, UNDERLINE, or BEAM
blink = true
overrideColor = Color(0xFF00FFCC)
}
gestures {
enableTouchToFocus = true
enableSelection = true
wheelScrollMultiplier = 1.5f
touchScrollSendsWheelOnly = true // Opt-in wheel-only touch scrolling for tmux/vim
}
}
TerminalRenderer(
terminalState = terminalState,
config = uiConfig,
onInput = { input ->
// Forward user keyboard input to PTY / SSH channel
},
onArrowKey = { arrow, shift ->
// Forward arrow navigation
},
onLog = { msg -> println("[TerminalLog] $msg") },
modifier = Modifier.fillMaxSize()
)
}Text selection is managed through TerminalSelection and SelectionState:
import com.johnan.terminal.core.TerminalSelection
// Programmatically extract text from a selection
val selection = TerminalSelection(startRow = 0, startCol = 0, endRow = 2, endCol = 40)
val selectedText = selection.extractText(screenBuffer)
// Selection containment check
val isInside = selection.contains(row = 1, col = 10)copy() returns a fully independent deep copy, including a partially parsed escape or UTF-8 sequence. This lets you
keep one headless emulator per received state (for example Mosh) and apply a diff to a copy of the reference state.
bellCount, clipboardWriteCount and lastClipboardWrite let you diff event-only state between snapshots, and
ScreenBuffer.isRowWrapped(row) exposes the soft-wrap flag.
val reference = TerminalEmulator(TerminalConfig(initialRows = 24, initialCols = 80))
reference.processOutput("hello")
val next = reference.copy() // callbacks are not inherited; pass them if needed
next.processOutput("\u001B[31m world") // diff applied to the copy only
val bellRang = next.bellCount > reference.bellCount
val clipboardChanged = next.clipboardWriteCount > reference.clipboardWriteCount
val firstRowWraps = next.getScreenBuffer().isRowWrapped(0)This project uses mise for reproducible toolchain management (JDK 21, Android SDK, Gradle, Node 20).
# Compile WebAssembly target
mise run compile-wasm
# Run local WebAssembly browser demo
mise run run-demo
# Run native JVM Desktop demo
mise run run-jvm
# Assemble Android debug APK (demo-mobile)
mise run assemble-android
# Compile iOS simulator target (demo-mobile)
mise run compile-ios-demo
# Build all targets
mise run build
# Execute all multiplatform unit tests
mise exec -- ./gradlew allTests
# Run fast unit test suite
mise exec -- ./gradlew test
# Verify Kotlin code formatting and linting
mise exec -- ./gradlew ktlintCheckContributions are welcome! Please read through our community guides:
Distributed under the Apache License, Version 2.0. Free for commercial and private use with attribution required and express patent grant.