ClojureHome

Choose documentation 1

latest

Current version

latest
English

Unreleased io.github.libtmux:libtmux-clojure has not been released. Its API may change during development. This site's structure and URLs may change too.

libtmux for Clojure

Clojure-based tmux client

tmux client library for Clojure. Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents

Built on the Java library for JVM. The packages share their source repository and releases.

(require '[libtmux.core :as tm]
'[libtmux.data :as data])
(import '[java.lang ProcessHandle]
'[java.nio.file Files Path]
'[java.nio.file.attribute FileAttribute]
'[java.time Duration]
[java.util.concurrent TimeUnit])
(let [attrs (make-array FileAttribute 0)
root (Path/of "/tmp/libtmux-java-dev" (make-array String 0))
_ (Files/createDirectories root attrs)
directory (Files/createTempDirectory root "clojure-home-" attrs)
socket (.resolve directory "tmux.sock")
daemon (atom nil)]
(try
(with-open [server (tm/open! {:socket-path socket
:binary (or (System/getenv "TMUX_BIN") "tmux")
:config-file "/dev/null"})]
(try
(let [session (tm/new-session! server {:name "demo"
:window-name "work"
:command ["/bin/sh"]})
window (data/exactly-one (data/windows session))
pane (data/exactly-one (data/panes window))
_ (reset! daemon
(.get (ProcessHandle/of
(get-in session [:tmux/identity :tmux/pid]))))]
(tm/rename! (:tmux/ref window) "editor")
(assert (= "work" (:window/name window)))
(tm/split-pane! (:tmux/ref pane)
{:direction :right :command ["/bin/cat"]})
(tm/new-window! (:tmux/ref session)
{:name "logs" :detached? true :command ["/bin/cat"]})
(tm/send-literal! (:tmux/ref pane)
"printf 'hello from %s\\n' Clojure")
(tm/send-keys! (:tmux/ref pane) ["Enter"])
(assert (#{:appeared :present-at-entry}
(tm/await-text! (:tmux/ref pane) "hello from Clojure"
(Duration/ofSeconds 2))))
(let [snapshot (tm/snapshot! server)
observed (data/exactly-one (:tmux/sessions snapshot))]
(assert (= "demo" (:session/name observed)))
(println "session" (:session/name observed))
(doseq [entry (data/windows observed)]
(println (str " window " (:window/name entry) ": "
(count (data/panes entry)) " panes")))
(assert (some #{"hello from Clojure"}
(tm/capture! (:tmux/ref pane))))
(println "output=hello from Clojure")))
(finally
(tm/kill-server! server)
(when-let [^ProcessHandle process @daemon]
(.get (.onExit process) 1 TimeUnit/SECONDS)))))
(finally
(if-let [^ProcessHandle process @daemon]
(when-not (.isAlive process)
(Files/deleteIfExists socket)
(Files/deleteIfExists directory))
(when-not (Files/exists socket (make-array java.nio.file.LinkOption 0))
(Files/deleteIfExists directory))))))
(require '[libtmux.core :as tm]
'[libtmux.data :as data])
(import '[java.lang ProcessHandle]
'[java.nio.file Files Path]
'[java.nio.file.attribute FileAttribute]
'[java.time Duration]
[java.util.concurrent TimeUnit])
(let [attrs (make-array FileAttribute 0)
root (Path/of "/tmp/libtmux-java-dev" (make-array String 0))
_ (Files/createDirectories root attrs)
directory (Files/createTempDirectory root "clojure-home-" attrs)
socket (.resolve directory "tmux.sock")
daemon (atom nil)]
(try
(with-open [server (tm/open! {:socket-path socket
:binary (or (System/getenv "TMUX_BIN") "tmux")
:config-file "/dev/null"})]
(try
(let [session (tm/new-session! server {:name "demo"
:window-name "work"
:command ["/bin/sh"]})
window (data/exactly-one (data/windows session))
pane (data/exactly-one (data/panes window))
_ (reset! daemon
(.get (ProcessHandle/of
(get-in session [:tmux/identity :tmux/pid]))))]
(tm/rename! (:tmux/ref window) "editor")
(assert (= "work" (:window/name window)))
(tm/split-pane! (:tmux/ref pane)
{:direction :right :command ["/bin/cat"]})
(tm/new-window! (:tmux/ref session)
{:name "logs" :detached? true :command ["/bin/cat"]})
(tm/send-literal! (:tmux/ref pane)
"printf 'hello from %s\\n' Clojure")
(tm/send-keys! (:tmux/ref pane) ["Enter"])
(assert (#{:appeared :present-at-entry}
(tm/await-text! (:tmux/ref pane) "hello from Clojure"
(Duration/ofSeconds 2))))
(let [snapshot (tm/snapshot! server)
observed (data/exactly-one (:tmux/sessions snapshot))]
(assert (= "demo" (:session/name observed)))
(println "session" (:session/name observed))
(doseq [entry (data/windows observed)]
(println (str " window " (:window/name entry) ": "
(count (data/panes entry)) " panes")))
(assert (some #{"hello from Clojure"}
(tm/capture! (:tmux/ref pane))))
(println "output=hello from Clojure")))
(catch Exception failure
(throw (ex-info "Clojure tmux quickstart failed"
{:tmux/failure (tm/error-summary failure)} failure)))
(finally
(tm/kill-server! server)
(when-let [^ProcessHandle process @daemon]
(.get (.onExit process) 1 TimeUnit/SECONDS)))))
(finally
(if-let [^ProcessHandle process @daemon]
(when-not (.isAlive process)
(Files/deleteIfExists socket)
(Files/deleteIfExists directory))
(when-not (Files/exists socket (make-array java.nio.file.LinkOption 0))
(Files/deleteIfExists directory))))))
(require '[libtmux.core :as tm]
'[libtmux.data :as data])
(import '[java.lang ProcessHandle]
'[java.nio.file Files Path]
'[java.nio.file.attribute FileAttribute]
'[java.time Duration]
[java.util.concurrent TimeUnit])
(let [attrs (make-array FileAttribute 0)
root (Path/of "/tmp/libtmux-java-dev" (make-array String 0))
_ (Files/createDirectories root attrs)
directory (Files/createTempDirectory root "clojure-home-" attrs)
socket (.resolve directory "tmux.sock")
daemon (atom nil)]
(try
(with-open [server (tm/open! {:socket-path socket
:binary (or (System/getenv "TMUX_BIN") "tmux")
:config-file "/dev/null"})]
(try
(let [session (tm/new-session! server {:name "demo"
:window-name "work"
:command ["/bin/sh"]})
window (data/exactly-one (data/windows session))
pane (data/exactly-one (data/panes window))
_ (reset! daemon
(.get (ProcessHandle/of
(get-in session [:tmux/identity :tmux/pid]))))]
(tm/rename! (:tmux/ref window) "editor")
(assert (= "work" (:window/name window)))
(tm/split-pane! (:tmux/ref pane)
{:direction :right :command ["/bin/cat"]})
(tm/new-window! (:tmux/ref session)
{:name "logs" :detached? true :command ["/bin/cat"]})
(tm/send-literal! (:tmux/ref pane)
"printf 'hello from %s\\n' Clojure")
(tm/send-keys! (:tmux/ref pane) ["Enter"])
(assert (#{:appeared :present-at-entry}
(tm/await-text! (:tmux/ref pane) "hello from Clojure"
(Duration/ofSeconds 2))))
(let [snapshot (tm/snapshot! server)
observed (data/exactly-one (:tmux/sessions snapshot))]
(assert (= "demo" (:session/name observed)))
(println "session" (:session/name observed))
(doseq [entry (data/windows observed)]
(println (str " window " (:window/name entry) ": "
(count (data/panes entry)) " panes")))
(assert (some #{"hello from Clojure"}
(tm/capture! (:tmux/ref pane))))
(println "output=hello from Clojure")))
(finally
(tm/kill-server! server)
(when-let [^ProcessHandle process @daemon]
(.get (.onExit process) 1 TimeUnit/SECONDS)))))
(finally
(if-let [^ProcessHandle process @daemon]
(when-not (.isAlive process)
(Files/deleteIfExists socket)
(Files/deleteIfExists directory))
(when-not (Files/exists socket (make-array java.nio.file.LinkOption 0))
(Files/deleteIfExists directory)))))
(assert (not (Files/exists directory
(make-array java.nio.file.LinkOption 0)))))
(require '[libtmux.core :as tm]
'[libtmux.data :as data])
(import '[java.lang ProcessHandle]
'[java.nio.file Files Path]
'[java.nio.file.attribute FileAttribute]
'[java.time Duration]
[java.util.concurrent TimeUnit])
(let [attrs (make-array FileAttribute 0)
root (Path/of "/tmp/libtmux-java-dev" (make-array String 0))
_ (Files/createDirectories root attrs)
directory (Files/createTempDirectory root "clojure-home-" attrs)
socket (.resolve directory "tmux.sock")
daemon (atom nil)]
(try
(with-open [server (tm/open! {:socket-path socket
:binary (or (System/getenv "TMUX_BIN") "tmux")
:config-file "/dev/null"})]
(try
(let [session (tm/new-session! server {:name "demo"
:window-name "work"
:command ["/bin/sh"]})
window (data/exactly-one (data/windows session))
pane (data/exactly-one (data/panes window))
_ (reset! daemon
(.get (ProcessHandle/of
(get-in session [:tmux/identity :tmux/pid]))))]
(tm/rename! (:tmux/ref window) "editor")
(assert (= "work" (:window/name window)))
(tm/split-pane! (:tmux/ref pane)
{:direction :right :command ["/bin/cat"]})
(tm/new-window! (:tmux/ref session)
{:name "logs" :detached? true :command ["/bin/cat"]})
(tm/send-literal! (:tmux/ref pane)
"printf 'hello from %s\\n' Clojure")
(tm/send-keys! (:tmux/ref pane) ["Enter"])
(assert (#{:appeared :present-at-entry}
(tm/await-text! (:tmux/ref pane) "hello from Clojure"
(Duration/ofSeconds 2))))
(let [snapshot (tm/snapshot! server)
observed (data/exactly-one (:tmux/sessions snapshot))]
(assert (= "demo" (:session/name observed)))
(println "session" (:session/name observed))
(doseq [entry (data/windows observed)]
(println (str " window " (:window/name entry) ": "
(count (data/panes entry)) " panes")))
(assert (some #{"hello from Clojure"}
(tm/capture! (:tmux/ref pane))))
(println "output=hello from Clojure")))
(catch Exception failure
(throw (ex-info "Clojure tmux quickstart failed"
{:tmux/failure (tm/error-summary failure)} failure)))
(finally
(tm/kill-server! server)
(when-let [^ProcessHandle process @daemon]
(.get (.onExit process) 1 TimeUnit/SECONDS)))))
(finally
(if-let [^ProcessHandle process @daemon]
(when-not (.isAlive process)
(Files/deleteIfExists socket)
(Files/deleteIfExists directory))
(when-not (Files/exists socket (make-array java.nio.file.LinkOption 0))
(Files/deleteIfExists directory)))))
(assert (not (Files/exists directory
(make-array java.nio.file.LinkOption 0)))))
Example details
Run this example

JDK 25 or newer, Clojure CLI 1.12, Git, tmux 3.3a or newer, /bin/sh and /bin/cat on Linux or macOS. The Clojure artifacts are unpublished; install the pinned source into the isolated Maven repository first.

Save the program above as quickstart.clj in a new directory, along with the files below.

Every view closes its client and stops its private server. If shutdown fails, its socket directory stays available for recovery. Cleanup also checks directory removal. 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.

deps.edn
{:paths ["."]
:mvn/local-repo "m2"
:deps {org.clojure/clojure {:mvn/version "1.12.6"}
io.github.libtmux/libtmux-clojure {:mvn/version "0.1.2-SNAPSHOT"}}}

Setup and run

git clone \
--branch master \
--single-branch \
https://github.com/libtmux/libtmux-java libtmux-source && \
git -C libtmux-source checkout --detach 941f2162062b6d118c68b7d1c23417e0d2cea124 && \
(cd libtmux-source && ./gradlew \
:libtmux:publishToMavenLocal \
:libtmux-clojure:publishToMavenLocal \
--max-workers=1 \
--console=plain \
"-Dmaven.repo.local=$PWD/../m2")
clojure -J-Dclojure.main.report=stderr -M quickstart.clj

Expected output

session demo
window editor: 2 panes
window logs: 1 panes
output=hello from Clojure

Install io.github.libtmux:libtmux-clojure

$ git clone --branch master https://github.com/libtmux/libtmux-java && (cd \
    libtmux-java && ./gradlew :libtmux:publishToMavenLocal \
    :libtmux-clojure:publishToMavenLocal)

Packages and integrations

API references

Esc

Type to search.

Reference previewOpen reference