
Simplifies terminal input handling, unifies keyboard into a byte stream ready for PTY/SSH, supports RAW/TEXT modes, maps virtual keys to ANSI sequences, and offers UI integration.
KMP Terminal Input is a Kotlin Multiplatform library that simplifies terminal input handling on mobile devices. It provides a unified API to consume character streams and control input modes, bridging the gap between native mobile keyboards (IMEs) and terminal emulators.
Key features:
Flow<ByteArray>, ready to be piped into a PTY or SSH session.TerminalInputContainer composable for easy integration.TerminalView (Android) and TerminalInputView (iOS) for direct platform usage.Add the dependency to your commonMain source set in build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.isseikz:kmp-terminal-input:1.0.0")
}
}
}Ensure mavenCentral() is in your repositories configuration:
repositories {
mavenCentral()
}The simplest way to use this library is with TerminalInputContainer and rememberTerminalInputContainerState():
import io.github.isseikz.kmpinput.*
@Composable
fun TerminalScreen() {
val terminalState = rememberTerminalInputContainerState()
val logs = remember { mutableStateListOf<String>() }
// Collect keyboard input
LaunchedEffect(terminalState.isReady) {
terminalState.ptyInputStream.collect { bytes ->
val text = bytes.decodeToString()
logs.add(text)
// Send bytes to your PTY, SSH, or process
}
}
Column {
// Mode switching buttons
Row {
Button(onClick = { terminalState.setInputMode(InputMode.RAW) }) {
Text("RAW Mode")
}
Button(onClick = { terminalState.setInputMode(InputMode.TEXT) }) {
Text("TEXT Mode")
}
}
// Wrap your content with TerminalInputContainer
// Tapping inside will show the keyboard
TerminalInputContainer(
state = terminalState,
modifier = Modifier.weight(1f).fillMaxWidth()
) {
// Your terminal display content here
LazyColumn {
items(logs.size) { index ->
Text(logs[index])
}
}
}
}
}| Property/Method | Description |
|---|---|
isReady |
Whether the handler is ready and attached |
uiState |
StateFlow of current input mode and composing state |
ptyInputStream |
Flow of byte arrays from keyboard input |
setInputMode(mode) |
Switch between RAW and TEXT mode |
injectKey(key, modifiers) |
Programmatically inject a virtual key |
injectString(text) |
Programmatically inject text |
Use TerminalView directly in your Activity or Fragment:
XML Layout:
<io.github.isseikz.kmpinput.TerminalView
android:id="@+id/terminalView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your terminal content here -->
<TextView
android:id="@+id/terminalOutput"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</io.github.isseikz.kmpinput.TerminalView>Kotlin Code:
val terminalView = findViewById<TerminalView>(R.id.terminalView)
val handler = terminalView.handler
// Initialize
handler.attach(lifecycleScope)
// Collect input
lifecycleScope.launch {
handler.ptyInputStream.collect { bytes ->
// Handle input
}
}
// Programmatically show/hide keyboard
terminalView.showKeyboard()
terminalView.hideKeyboard()Use TerminalInputView (a UIView subclass) in your layout:
import Shared
import UIKit
class ViewController: UIViewController {
let terminalInputView = TerminalInputView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
terminalInputView.frame = view.bounds
view.addSubview(terminalInputView)
let handler = terminalInputView.handler
// Bind handler to your shared logic...
}
func showKeyboard() {
terminalInputView.becomeFirstResponder()
}
}| Mode | Description | Use Case |
|---|---|---|
| RAW | No predictive text, autocorrect disabled | Shell, Vim, SSH |
| TEXT | Full IME support, predictive text enabled | AI chat, natural language input |
TerminalView extends FrameLayout and uses a custom InputConnection. Wraps child views and captures touch events to show keyboard.TerminalInputView implements UITextInput protocol with full marked text support for Japanese IME.TerminalInputCore normalizes events into ANSI sequences (e.g., Up Arrow -> \u001b[A, Ctrl+C -> \u0003).TerminalInputContainer provides cross-platform Compose integration using expect/actual pattern.Check the composeApp module in this repository for a complete working example using Compose Multiplatform.
onLongPress callback in TerminalInputContainer is currently only implemented for Android. iOS support is planned for a future release.Apache License 2.0
KMP Terminal Input is a Kotlin Multiplatform library that simplifies terminal input handling on mobile devices. It provides a unified API to consume character streams and control input modes, bridging the gap between native mobile keyboards (IMEs) and terminal emulators.
Key features:
Flow<ByteArray>, ready to be piped into a PTY or SSH session.TerminalInputContainer composable for easy integration.TerminalView (Android) and TerminalInputView (iOS) for direct platform usage.Add the dependency to your commonMain source set in build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.isseikz:kmp-terminal-input:1.0.0")
}
}
}Ensure mavenCentral() is in your repositories configuration:
repositories {
mavenCentral()
}The simplest way to use this library is with TerminalInputContainer and rememberTerminalInputContainerState():
import io.github.isseikz.kmpinput.*
@Composable
fun TerminalScreen() {
val terminalState = rememberTerminalInputContainerState()
val logs = remember { mutableStateListOf<String>() }
// Collect keyboard input
LaunchedEffect(terminalState.isReady) {
terminalState.ptyInputStream.collect { bytes ->
val text = bytes.decodeToString()
logs.add(text)
// Send bytes to your PTY, SSH, or process
}
}
Column {
// Mode switching buttons
Row {
Button(onClick = { terminalState.setInputMode(InputMode.RAW) }) {
Text("RAW Mode")
}
Button(onClick = { terminalState.setInputMode(InputMode.TEXT) }) {
Text("TEXT Mode")
}
}
// Wrap your content with TerminalInputContainer
// Tapping inside will show the keyboard
TerminalInputContainer(
state = terminalState,
modifier = Modifier.weight(1f).fillMaxWidth()
) {
// Your terminal display content here
LazyColumn {
items(logs.size) { index ->
Text(logs[index])
}
}
}
}
}| Property/Method | Description |
|---|---|
isReady |
Whether the handler is ready and attached |
uiState |
StateFlow of current input mode and composing state |
ptyInputStream |
Flow of byte arrays from keyboard input |
setInputMode(mode) |
Switch between RAW and TEXT mode |
injectKey(key, modifiers) |
Programmatically inject a virtual key |
injectString(text) |
Programmatically inject text |
Use TerminalView directly in your Activity or Fragment:
XML Layout:
<io.github.isseikz.kmpinput.TerminalView
android:id="@+id/terminalView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your terminal content here -->
<TextView
android:id="@+id/terminalOutput"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</io.github.isseikz.kmpinput.TerminalView>Kotlin Code:
val terminalView = findViewById<TerminalView>(R.id.terminalView)
val handler = terminalView.handler
// Initialize
handler.attach(lifecycleScope)
// Collect input
lifecycleScope.launch {
handler.ptyInputStream.collect { bytes ->
// Handle input
}
}
// Programmatically show/hide keyboard
terminalView.showKeyboard()
terminalView.hideKeyboard()Use TerminalInputView (a UIView subclass) in your layout:
import Shared
import UIKit
class ViewController: UIViewController {
let terminalInputView = TerminalInputView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
terminalInputView.frame = view.bounds
view.addSubview(terminalInputView)
let handler = terminalInputView.handler
// Bind handler to your shared logic...
}
func showKeyboard() {
terminalInputView.becomeFirstResponder()
}
}| Mode | Description | Use Case |
|---|---|---|
| RAW | No predictive text, autocorrect disabled | Shell, Vim, SSH |
| TEXT | Full IME support, predictive text enabled | AI chat, natural language input |
TerminalView extends FrameLayout and uses a custom InputConnection. Wraps child views and captures touch events to show keyboard.TerminalInputView implements UITextInput protocol with full marked text support for Japanese IME.TerminalInputCore normalizes events into ANSI sequences (e.g., Up Arrow -> \u001b[A, Ctrl+C -> \u0003).TerminalInputContainer provides cross-platform Compose integration using expect/actual pattern.Check the composeApp module in this repository for a complete working example using Compose Multiplatform.
onLongPress callback in TerminalInputContainer is currently only implemented for Android. iOS support is planned for a future release.Apache License 2.0