
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
kmp-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.altBuffer for vim/htop/less).ASK, ALWAYS_ALLOW, ALWAYS_DENY).wasmJs)jvm)androidMain)iosArm64, iosSimulatorArm64)kmp-terminal/
├── terminal-core/ # Pure Kotlin Multiplatform emulation engine & screen buffer
├── terminal-ui/ # Compose Multiplatform canvas renderer & UI components
├── demo-web/ # WebAssembly (wasmJs) interactive demo
└── demo-jvm/ # JVM Desktop Compose demo
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.0")
// Optional: Compose Multiplatform canvas renderer & gestures
implementation("io.github.johnan:terminal-ui:0.3.0")
}
}
}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)This project uses mise for reproducible toolchain management (JDK 21, Android SDK, Gradle).
# Compile WebAssembly demo
mise run compile-wasm
# Run local WebAssembly demo server
mise run run-demo
# Execute all multiplatform unit tests
mise exec -- ./gradlew allTestsContributions 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
kmp-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.altBuffer for vim/htop/less).ASK, ALWAYS_ALLOW, ALWAYS_DENY).wasmJs)jvm)androidMain)iosArm64, iosSimulatorArm64)kmp-terminal/
├── terminal-core/ # Pure Kotlin Multiplatform emulation engine & screen buffer
├── terminal-ui/ # Compose Multiplatform canvas renderer & UI components
├── demo-web/ # WebAssembly (wasmJs) interactive demo
└── demo-jvm/ # JVM Desktop Compose demo
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.0")
// Optional: Compose Multiplatform canvas renderer & gestures
implementation("io.github.johnan:terminal-ui:0.3.0")
}
}
}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)This project uses mise for reproducible toolchain management (JDK 21, Android SDK, Gradle).
# Compile WebAssembly demo
mise run compile-wasm
# Run local WebAssembly demo server
mise run run-demo
# Execute all multiplatform unit tests
mise exec -- ./gradlew allTestsContributions 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.