# libtmux for Clojure

Source: https://libtmux.org/en/clojure/latest/

> libtmux for Clojure, version latest.

![](https://libtmux.org/en/brand/clojure/library/logo.svg)

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](https://libtmux.org/en/java/latest/) for JVM. The packages share their source repository and releases.

```clojure
(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))))))
```

```clojure
(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))))))
```

```clojure
(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)))))
```

```clojure
(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

\+ Error handling + Cleanup

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

```clojure
{: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

```bash
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")
```

```bash
clojure -J-Dclojure.main.report=stderr -M quickstart.clj
```

### Expected output

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

[Core Library documentation →](https://libtmux.org/en/clojure/latest/core/)[More Clojure examples →](https://libtmux.org/en/clojure/latest/examples/)

### Install `io.github.libtmux:libtmux-clojure`

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

### [Guides](https://libtmux.org/en/clojure/latest/guides/)

[Task guides for Clojure.](https://libtmux.org/en/clojure/latest/guides/)

### [Examples](https://libtmux.org/en/clojure/latest/examples/)

[Complete programs, sourced from the code each port tests.](https://libtmux.org/en/clojure/latest/examples/)

### [Concepts](https://libtmux.org/en/clojure/latest/concepts/)

[The domain model every port shares.](https://libtmux.org/en/clojure/latest/concepts/)

## Packages and integrations

### [core.async adapter](https://libtmux.org/en/clojure/latest/guides/core-async/)

[Use the optional, separately installable adapter for bounded event channels and explicit shutdown.](https://libtmux.org/en/clojure/latest/guides/core-async/)

## API references

### [Official reference](https://libtmux.org/en/clojure/latest/reference/)

[Clojure API documentation and links to its package sources.](https://libtmux.org/en/clojure/latest/reference/)
