tmux client library for Kotlin
Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents.
Guides
Task guides for Kotlin.
Examples
Complete programs, sourced from the code each port tests.
API reference
Types and functions of the Kotlin API.
Install
implementation("io.github.libtmux:libtmux-kotlin:0.1.0")At a glance
Server → Session → Window → Pane. Each level of the tmux hierarchy has its own entry in the Kotlin API, with the tmux object it wraps.
| Type | Wraps |
|---|---|
io.github.libtmux.kotlin.Server | A tmux server |
io.github.libtmux.kotlin.Session | A session, ID $n |
io.github.libtmux.kotlin.Window | A window in a session, ID @n |
io.github.libtmux.kotlin.Pane | A pane in a window, ID %n |
What it does
Start with Kotlin
import io.github.libtmux.ServerConfigimport io.github.libtmux.ServerEndpointimport io.github.libtmux.TextOutcomeimport io.github.libtmux.kotlin.*import java.nio.file.Pathimport java.time.Durationimport java.util.UUIDimport kotlinx.coroutines.runBlockingimport kotlin.time.Duration.Companion.seconds
fun main() = runBlocking { val socket = "libtmux-home-${UUID.randomUUID()}" val config = ServerConfig.builder() .endpoint(ServerEndpoint.namedSocket(socket)) .configFile(Path.of("/dev/null")) .defaultTimeout(Duration.ofSeconds(5)) .build()
withServer(config) { server -> val session = server.newSession("work") val pane = session.activeWindow?.activePane ?: error("no active pane")
pane.sendLine("printf '\\nrea%s\\n' dy") val outcome = pane.awaitText("ready", timeout = 5.seconds) check(outcome != TextOutcome.TIMED_OUT)
val lines = pane.capture() check(lines.any { it.trim() == "ready" })
val run = pane.run("echo hi && exit 3", timeout = 5.seconds) check(run.exitStatus.orNull() == 3)
println("ready; exit ${run.exitStatus.orNull()}") }}import io.github.libtmux.ServerConfigimport io.github.libtmux.ServerEndpointimport io.github.libtmux.TextOutcomeimport io.github.libtmux.kotlin.*import java.nio.file.Pathimport java.time.Durationimport java.util.UUIDimport kotlinx.coroutines.runBlockingimport kotlin.time.Duration.Companion.seconds
fun main() = runBlocking { val socket = "libtmux-home-${UUID.randomUUID()}" val config = ServerConfig.builder() .endpoint(ServerEndpoint.namedSocket(socket)) .configFile(Path.of("/dev/null")) .defaultTimeout(Duration.ofSeconds(5)) .build()
withServer(config) { server -> try { val session = server.newSession("work") val pane = session.activeWindow?.activePane ?: error("no active pane")
pane.sendLine("printf '\\nrea%s\\n' dy") val outcome = pane.awaitText("ready", timeout = 5.seconds) check(outcome != TextOutcome.TIMED_OUT)
val lines = pane.capture() check(lines.any { it.trim() == "ready" })
val run = pane.run("echo hi && exit 3", timeout = 5.seconds) check(run.exitStatus.orNull() == 3)
println("ready; exit ${run.exitStatus.orNull()}") } catch (error: Exception) { throw IllegalStateException( "tmux quickstart failed", error ) } }}import io.github.libtmux.ServerConfigimport io.github.libtmux.ServerEndpointimport io.github.libtmux.TextOutcomeimport io.github.libtmux.kotlin.*import java.nio.file.Pathimport java.time.Durationimport java.util.UUIDimport kotlinx.coroutines.NonCancellableimport kotlinx.coroutines.runBlockingimport kotlinx.coroutines.withContextimport kotlin.time.Duration.Companion.seconds
fun main() = runBlocking { val socket = "libtmux-home-${UUID.randomUUID()}" val config = ServerConfig.builder() .endpoint(ServerEndpoint.namedSocket(socket)) .configFile(Path.of("/dev/null")) .defaultTimeout(Duration.ofSeconds(5)) .build()
withServer(config) { server -> try { val session = server.newSession("work") val pane = session.activeWindow?.activePane ?: error("no active pane")
pane.sendLine("printf '\\nrea%s\\n' dy") val outcome = pane.awaitText("ready", timeout = 5.seconds) check(outcome != TextOutcome.TIMED_OUT)
val lines = pane.capture() check(lines.any { it.trim() == "ready" })
val run = pane.run("echo hi && exit 3", timeout = 5.seconds) check(run.exitStatus.orNull() == 3)
println("ready; exit ${run.exitStatus.orNull()}") } finally { withContext(NonCancellable) { server.killServer() } } }}import io.github.libtmux.ServerConfigimport io.github.libtmux.ServerEndpointimport io.github.libtmux.TextOutcomeimport io.github.libtmux.kotlin.*import java.nio.file.Pathimport java.time.Durationimport java.util.UUIDimport kotlinx.coroutines.NonCancellableimport kotlinx.coroutines.runBlockingimport kotlinx.coroutines.withContextimport kotlin.time.Duration.Companion.seconds
fun main() = runBlocking { val socket = "libtmux-home-${UUID.randomUUID()}" val config = ServerConfig.builder() .endpoint(ServerEndpoint.namedSocket(socket)) .configFile(Path.of("/dev/null")) .defaultTimeout(Duration.ofSeconds(5)) .build()
withServer(config) { server -> try { val session = server.newSession("work") val pane = session.activeWindow?.activePane ?: error("no active pane")
pane.sendLine("printf '\\nrea%s\\n' dy") val outcome = pane.awaitText("ready", timeout = 5.seconds) check(outcome != TextOutcome.TIMED_OUT)
val lines = pane.capture() check(lines.any { it.trim() == "ready" })
val run = pane.run("echo hi && exit 3", timeout = 5.seconds) check(run.exitStatus.orNull() == 3)
println("ready; exit ${run.exitStatus.orNull()}") } catch (error: Exception) { throw IllegalStateException( "tmux quickstart failed", error ) } finally { withContext(NonCancellable) { server.killServer() } } }}Run this example
JDK 25 and tmux on Linux/macOS; Gradle resolves Kotlin 2.4.10
Save the program above as src/main/kotlin/Main.kt in a new directory, along with the files below.
Cleanup is omitted; the private tmux server and its temporary files may remain. Select Cleanup to remove them when the program finishes. The program stops its private tmux server and removes any temporary files it created when it finishes. Error handling adds diagnostics; every version checks required results and deadlines.
build.gradle.kts
plugins { kotlin("jvm") version "2.4.10" application}
repositories { mavenCentral() }
dependencies { implementation("io.github.libtmux:libtmux-kotlin:0.1.0")}
java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } }kotlin { jvmToolchain(25) }
application { mainClass.set(providers.gradleProperty("exampleMain")) }settings.gradle.kts
rootProject.name = "api-example"gradle.properties
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8kotlin.daemon.jvmargs=-Xmx2gorg.gradle.workers.max=2Setup and run
# Save the files above at their displayed relative paths.git clone https://github.com/libtmux/libtmux-java.git libtmux-sourcegit -C libtmux-source checkout --detach 5c181887918351119bfdb3a046719fa792048e59./libtmux-source/gradlew --project-dir . --console=plain --no-daemon --max-workers=2 \ installDist -PexampleMain=MainKtSHELL=/bin/sh build/install/api-example/bin/api-exampleExpected output
ready; exit 3Library revision: 5c1818879183.
Typed API
- Typed objects instead of parsed format strings:
io.github.libtmux.kotlin.Pane - Addressing by tmux ID:
io.github.libtmux.kotlin.Pane.id - Filtering and queries over tmux objects:
io.github.libtmux.kotlin.Iterable.filter
What you get over the tmux CLI
- Connect to or start a tmux server:
io.github.libtmux.kotlin.Server.Companion.open - Traversal from a server down to its panes:
io.github.libtmux.kotlin.Server.panes - Send keys to a pane:
io.github.libtmux.kotlin.Pane.sendKeys - Watch for changes through tmux control mode:
io.github.libtmux.kotlin.ControlClient - Capture pane contents:
io.github.libtmux.kotlin.Pane.capture - Arguments passed as a list, with no shell quoting:
io.github.libtmux.kotlin.Server.cmd - Async operations:
io.github.libtmux.kotlin.Server.panes