Examples
Start with Capture pane output for a standalone program with imports, Gradle files, and private-server cleanup.
Read a Flow¶
Read pushed pane output as a coroutine Flow and cancel a pending wait. The complete program belongs to the Java repository’s examples module; that module also supplies WatchPaneOutput, and its test starts an isolated tmux server before calling this program’s main. Run ./gradlew :examples:test from that repository.
package io.github.libtmux.examples
import io.github.libtmux.ServerConfigimport io.github.libtmux.ServerEndpointimport io.github.libtmux.control.Deliveryimport io.github.libtmux.kotlin.Serverimport io.github.libtmux.kotlin.awaitimport io.github.libtmux.kotlin.sendimport io.github.libtmux.kotlin.sessionsimport io.github.libtmux.kotlin.withControlimport io.github.libtmux.kotlin.withServerimport java.nio.file.Pathimport kotlin.time.Durationimport kotlin.time.Duration.Companion.millisecondsimport kotlin.time.Duration.Companion.secondsimport kotlinx.coroutines.flow.firstimport kotlinx.coroutines.flow.mapimport kotlinx.coroutines.runBlockingimport kotlinx.coroutines.withTimeoutOrNull
/** * Reads a pane's output as a `Flow`, then gives up on a wait by cancelling it. * * ``` * java -cp ... io.github.libtmux.examples.WatchWithFlowKt /tmp/libtmux-java-dev/demo/s * ``` */fun main(args: Array<String>) { val socket = Path.of(args.getOrElse(0) { "/tmp/libtmux-java-dev/demo/s" }) runBlocking { println(watchWithFlow(socket, 10.seconds)) }}
/** * Collects output until the line `echo` printed arrives, then starts a channel wait that nothing * will signal and cancels it after a moment. Cancelling ends the coroutine at once and is not a * timeout; the tmux client behind the wait is stopped with it. * * @return one line for each: whether the echo arrived, and whether the wait was cancelled */suspend fun watchWithFlow(socket: Path, deadline: Duration): String { val config = ServerConfig.builder().endpoint(ServerEndpoint.socketPath(socket)).build() return withServer(config) { server: Server -> val session = server.sessions().first() withControl(server, session) { control -> // control.output(...) is a cold Flow: the subscription it opens on first collection // closes when collection ends, is cancelled, or throws — nothing to close by hand. The // command goes in onSubscribed: sent before collecting, its output could arrive before // there was a subscription to hold it. val seen = StringBuilder() val echoed = withTimeoutOrNull(deadline) { control.output(capacity = 32) { control.send("send-keys", "-t", session.name, "echo flowed", "Enter") }.map { Delivery.kept(it).data }.first { chunk -> seen.append(chunk) WatchPaneOutput.printedLine(seen.toString(), "flowed") } } != null
val cancelled = withTimeoutOrNull(200.milliseconds) { server.channel("never-signalled").await(30.seconds) } == null
"echoed=$echoed\ncancelled=$cancelled" } }}