libtmux.core/snapshot!
Acquires an eager captured graph from an explicit server.
Source and package
- Qualified name
-
libtmux.core/ snapshot! - Module
- libtmux.core
- Declared in
libtmux.core- Package
- io.github.libtmux:libtmux-clojure
- Source
- libtmux-clojure/src/main/clojure/libtmux/core.clj
- Other languages
- Perlmethod Erlangfunction PowerShellcmdlet Juliafunction Elixirfunction Haskell Common Lispfunction Python RubyLuaTypeScriptRustmethod GoJavaC# C++ SwiftKotlinScalaOCamlfunction F#function
- (snapshot! server)
-
Public owner:
libtmux.coreDescription#
Traverse one observation with
libtmux.data/windowsandlibtmux.data/panes.Examples#
Connect to a private tmux server. Use JDK 25 or newer, the Clojure CLI, Git and tmux on Linux or macOS, with /bin/cat and /bin/sh available. These packages are unpublished. In an empty directory, install this exact source into the consumer's isolated Maven repository:
Terminal window $ git clone https://github.com/libtmux/libtmux-java libtmux-source && \git -C libtmux-source checkout --detach 5f88402151ff08fc0390dd438b90963692ec3fe4 && \(cd libtmux-source && ./gradlew \:libtmux:publishToMavenLocal \:libtmux-clojure:publishToMavenLocal \--console=plain \"-Dmaven.repo.local=$PWD/../m2")Save this file as
deps.edn. Dependencies resolve from the isolated m2 directory; the core.async dependency is only enabled by its alias. View source{:paths ["src/main/clojure"]:mvn/local-repo "m2":deps {org.clojure/clojure {:mvn/version "1.12.6"}io.github.libtmux/libtmux-clojure {:mvn/version "0.1.2-SNAPSHOT"}}:aliases{:core-async{:extra-deps{io.github.libtmux/libtmux-clojure-core-async {:mvn/version "0.1.2-SNAPSHOT"}}}}}Save this file as
run.sh. The launcher creates and stops a private server, including after program failure, and reports leaked servers as errors. View source#!/bin/shset -euif [ "$#" -eq 0 ]; thenprintf 'Usage: sh run.sh program [arguments...]\n' >&2exit 2fibinary=$(command -v "${TMUX_BIN:-tmux}")root=${API_RUN_ROOT:-/tmp/libtmux-java-dev}# A client that names no socket follows TMUX or LIBTMUX_SOCKET*, and otherwise lands on the default# socket under TMUX_TMPDIR. The program gets none of them, and a TMUX_TMPDIR of its own below.unset TMUX TMUX_PANEfor name in $(env | sed -n 's/^\(LIBTMUX_SOCKET[A-Za-z0-9_]*\)=.*/\1/p'); dounset "$name"donemkdir -p "$root"directory=$(mktemp -d "$root/api.XXXXXX")tmpdir=$(mktemp -d "$root/tmpdir.XXXXXX")socket="$directory/tmux.sock"config="$directory/tmux.conf"serves() {"$binary" -S "$1" display-message -p '#{pid}' >/dev/null 2>&1}# Stops the server on the socket, then waits for it to stop answering.stop() {"$binary" -S "$1" kill-server || return 1attempts=0while serves "$1" && [ "$attempts" -lt 60 ]; dosleep 0.05attempts=$((attempts + 1))done! serves "$1"}cleanup() {status=$?trap - 0 HUP INT TERM PIPE# A closed output must not end the cleanup, whose messages are the only thing left to write.trap '' PIPEset +estuck=left=if [ -S "$socket" ] && serves "$socket" && ! stop "$socket"; thenstuck="$stuck tmux.sock"fi# Every socket anywhere under either directory, without following links. Only one a server answers# on is a leak; the runner's own is expected to be there.old_ifs=$IFSIFS=''set -ffor candidate in $(find "$tmpdir" "$directory" -type s 2>/dev/null); do[ "$candidate" = "$socket" ] && continueserves "$candidate" || continuecase "$candidate" in"$tmpdir"/*) name=${candidate#"$tmpdir"/} ;;*) name=${candidate#"$directory"/} ;;esacleft="$left $name"stop "$candidate" || stuck="$stuck $name"doneset +fIFS=$old_ifsif [ -n "$stuck" ]; thenprintf 'tmux still answers on%s after kill-server; kept %s and %s under the runner root\n' \"$stuck" "${directory##*/}" "${tmpdir##*/}" >&2exit 1firm -rf "$directory" "$tmpdir" || exit 1if [ -n "$left" ]; thenprintf 'The program left a tmux server running:%s\n' "$left" >&2exit 1fiexit "$status"}trap cleanup 0trap 'exit 1' HUP INT TERM PIPE# A default socket path longer than this does not fit in sun_path.default_socket="$tmpdir/tmux-$(id -u)/default"if [ "$(printf '%s' "$default_socket" | wc -c)" -gt 100 ]; thenprintf 'API_RUN_ROOT is too long for a tmux socket\n' >&2exit 1fiTMUX_TMPDIR=$tmpdirexport TMUX_TMPDIRprintf 'set-option -g default-shell /bin/sh\n' > "$config""$binary" -S "$socket" -f "$config" new-session -d -s work-one -n editor /bin/cat"$binary" -S "$socket" split-window -h -t '=work-one:editor' /bin/cat"$binary" -S "$socket" new-window -d -t '=work-one' -n logs /bin/cat"$binary" -S "$socket" new-session -d -s work-two -n editor /bin/cat"$@" "$binary" "$socket" "$config""$binary" -S "$socket" has-session -t '=work-one'"$binary" -S "$socket" has-session -t '=work-two'Save the complete program as
src/main/clojure/libtmux/examples/api/connect.clj, creating its parent directories. Closing its client leaves the launcher's server running. View source(ns libtmux.examples.api.connect"Connect to the private server supplied by run.sh."(:require [libtmux.core :as tm]))(defn -main [& args](when-not (= 3 (count args))(throw (IllegalArgumentException. "expected: tmux-binary socket-path config-file")))(let [[binary socket config] args](with-open [server (tm/open! {:binary binary :socket-path socket:config-file config})](let [snapshot (tm/snapshot! server)](assert (seq (:tmux/sessions snapshot)))(println "connected=true")))))Run it with the private fixture. Set TMUX_BIN to select another tmux binary:
Terminal window $ sh run.sh clojure \-J-Dclojure.main.report=stderr \-M \-m libtmux.examples.api.connectExpected output:
connected=trueTraverse root and session windows in one captured graph. Use JDK 25 or newer, the Clojure CLI, Git and tmux on Linux or macOS, with /bin/cat and /bin/sh available. These packages are unpublished. In an empty directory, install this exact source into the consumer's isolated Maven repository:
Terminal window $ git clone https://github.com/libtmux/libtmux-java libtmux-source && \git -C libtmux-source checkout --detach 5f88402151ff08fc0390dd438b90963692ec3fe4 && \(cd libtmux-source && ./gradlew \:libtmux:publishToMavenLocal \:libtmux-clojure:publishToMavenLocal \--console=plain \"-Dmaven.repo.local=$PWD/../m2")Save this file as
deps.edn. Dependencies resolve from the isolated m2 directory; the core.async dependency is only enabled by its alias. View source{:paths ["src/main/clojure"]:mvn/local-repo "m2":deps {org.clojure/clojure {:mvn/version "1.12.6"}io.github.libtmux/libtmux-clojure {:mvn/version "0.1.2-SNAPSHOT"}}:aliases{:core-async{:extra-deps{io.github.libtmux/libtmux-clojure-core-async {:mvn/version "0.1.2-SNAPSHOT"}}}}}Save this file as
run.sh. The launcher creates and stops a private server, including after program failure, and reports leaked servers as errors. View source#!/bin/shset -euif [ "$#" -eq 0 ]; thenprintf 'Usage: sh run.sh program [arguments...]\n' >&2exit 2fibinary=$(command -v "${TMUX_BIN:-tmux}")root=${API_RUN_ROOT:-/tmp/libtmux-java-dev}# A client that names no socket follows TMUX or LIBTMUX_SOCKET*, and otherwise lands on the default# socket under TMUX_TMPDIR. The program gets none of them, and a TMUX_TMPDIR of its own below.unset TMUX TMUX_PANEfor name in $(env | sed -n 's/^\(LIBTMUX_SOCKET[A-Za-z0-9_]*\)=.*/\1/p'); dounset "$name"donemkdir -p "$root"directory=$(mktemp -d "$root/api.XXXXXX")tmpdir=$(mktemp -d "$root/tmpdir.XXXXXX")socket="$directory/tmux.sock"config="$directory/tmux.conf"serves() {"$binary" -S "$1" display-message -p '#{pid}' >/dev/null 2>&1}# Stops the server on the socket, then waits for it to stop answering.stop() {"$binary" -S "$1" kill-server || return 1attempts=0while serves "$1" && [ "$attempts" -lt 60 ]; dosleep 0.05attempts=$((attempts + 1))done! serves "$1"}cleanup() {status=$?trap - 0 HUP INT TERM PIPE# A closed output must not end the cleanup, whose messages are the only thing left to write.trap '' PIPEset +estuck=left=if [ -S "$socket" ] && serves "$socket" && ! stop "$socket"; thenstuck="$stuck tmux.sock"fi# Every socket anywhere under either directory, without following links. Only one a server answers# on is a leak; the runner's own is expected to be there.old_ifs=$IFSIFS=''set -ffor candidate in $(find "$tmpdir" "$directory" -type s 2>/dev/null); do[ "$candidate" = "$socket" ] && continueserves "$candidate" || continuecase "$candidate" in"$tmpdir"/*) name=${candidate#"$tmpdir"/} ;;*) name=${candidate#"$directory"/} ;;esacleft="$left $name"stop "$candidate" || stuck="$stuck $name"doneset +fIFS=$old_ifsif [ -n "$stuck" ]; thenprintf 'tmux still answers on%s after kill-server; kept %s and %s under the runner root\n' \"$stuck" "${directory##*/}" "${tmpdir##*/}" >&2exit 1firm -rf "$directory" "$tmpdir" || exit 1if [ -n "$left" ]; thenprintf 'The program left a tmux server running:%s\n' "$left" >&2exit 1fiexit "$status"}trap cleanup 0trap 'exit 1' HUP INT TERM PIPE# A default socket path longer than this does not fit in sun_path.default_socket="$tmpdir/tmux-$(id -u)/default"if [ "$(printf '%s' "$default_socket" | wc -c)" -gt 100 ]; thenprintf 'API_RUN_ROOT is too long for a tmux socket\n' >&2exit 1fiTMUX_TMPDIR=$tmpdirexport TMUX_TMPDIRprintf 'set-option -g default-shell /bin/sh\n' > "$config""$binary" -S "$socket" -f "$config" new-session -d -s work-one -n editor /bin/cat"$binary" -S "$socket" split-window -h -t '=work-one:editor' /bin/cat"$binary" -S "$socket" new-window -d -t '=work-one' -n logs /bin/cat"$binary" -S "$socket" new-session -d -s work-two -n editor /bin/cat"$@" "$binary" "$socket" "$config""$binary" -S "$socket" has-session -t '=work-one'"$binary" -S "$socket" has-session -t '=work-two'Save the complete program as
src/main/clojure/libtmux/examples/api/list_windows.clj, creating its parent directories. Closing its client leaves the launcher's server running. View source(ns libtmux.examples.api.list-windows"Traverse root and session windows from the same captured graph."(:require [clojure.string :as str][libtmux.core :as tm][libtmux.data :as data]))(defn -main [& args](when-not (= 3 (count args))(throw (IllegalArgumentException. "expected: tmux-binary socket-path config-file")))(let [[binary socket config] args](with-open [server (tm/open! {:binary binary :socket-path socket:config-file config})](let [snapshot (tm/snapshot! server)session (data/exactly-one(filter #(= "work-one" (:session/name %))(:tmux/sessions snapshot)))](println (str "windows="(str/join "," (sort (map :window/name (data/windows snapshot))))))(println (str "session-windows="(str/join "," (sort (map :window/name (data/windows session))))))))))Run it with the private fixture. Set TMUX_BIN to select another tmux binary:
Terminal window $ sh run.sh clojure \-J-Dclojure.main.report=stderr \-M \-m libtmux.examples.api.list-windowsExpected output:
windows=editor,editor,logssession-windows=editor,logsTraverse root, session, window and pane observations. Use JDK 25 or newer, the Clojure CLI, Git and tmux on Linux or macOS, with /bin/cat and /bin/sh available. These packages are unpublished. In an empty directory, install this exact source into the consumer's isolated Maven repository:
Terminal window $ git clone https://github.com/libtmux/libtmux-java libtmux-source && \git -C libtmux-source checkout --detach 5f88402151ff08fc0390dd438b90963692ec3fe4 && \(cd libtmux-source && ./gradlew \:libtmux:publishToMavenLocal \:libtmux-clojure:publishToMavenLocal \--console=plain \"-Dmaven.repo.local=$PWD/../m2")Save this file as
deps.edn. Dependencies resolve from the isolated m2 directory; the core.async dependency is only enabled by its alias. View source{:paths ["src/main/clojure"]:mvn/local-repo "m2":deps {org.clojure/clojure {:mvn/version "1.12.6"}io.github.libtmux/libtmux-clojure {:mvn/version "0.1.2-SNAPSHOT"}}:aliases{:core-async{:extra-deps{io.github.libtmux/libtmux-clojure-core-async {:mvn/version "0.1.2-SNAPSHOT"}}}}}Save this file as
run.sh. The launcher creates and stops a private server, including after program failure, and reports leaked servers as errors. View source#!/bin/shset -euif [ "$#" -eq 0 ]; thenprintf 'Usage: sh run.sh program [arguments...]\n' >&2exit 2fibinary=$(command -v "${TMUX_BIN:-tmux}")root=${API_RUN_ROOT:-/tmp/libtmux-java-dev}# A client that names no socket follows TMUX or LIBTMUX_SOCKET*, and otherwise lands on the default# socket under TMUX_TMPDIR. The program gets none of them, and a TMUX_TMPDIR of its own below.unset TMUX TMUX_PANEfor name in $(env | sed -n 's/^\(LIBTMUX_SOCKET[A-Za-z0-9_]*\)=.*/\1/p'); dounset "$name"donemkdir -p "$root"directory=$(mktemp -d "$root/api.XXXXXX")tmpdir=$(mktemp -d "$root/tmpdir.XXXXXX")socket="$directory/tmux.sock"config="$directory/tmux.conf"serves() {"$binary" -S "$1" display-message -p '#{pid}' >/dev/null 2>&1}# Stops the server on the socket, then waits for it to stop answering.stop() {"$binary" -S "$1" kill-server || return 1attempts=0while serves "$1" && [ "$attempts" -lt 60 ]; dosleep 0.05attempts=$((attempts + 1))done! serves "$1"}cleanup() {status=$?trap - 0 HUP INT TERM PIPE# A closed output must not end the cleanup, whose messages are the only thing left to write.trap '' PIPEset +estuck=left=if [ -S "$socket" ] && serves "$socket" && ! stop "$socket"; thenstuck="$stuck tmux.sock"fi# Every socket anywhere under either directory, without following links. Only one a server answers# on is a leak; the runner's own is expected to be there.old_ifs=$IFSIFS=''set -ffor candidate in $(find "$tmpdir" "$directory" -type s 2>/dev/null); do[ "$candidate" = "$socket" ] && continueserves "$candidate" || continuecase "$candidate" in"$tmpdir"/*) name=${candidate#"$tmpdir"/} ;;*) name=${candidate#"$directory"/} ;;esacleft="$left $name"stop "$candidate" || stuck="$stuck $name"doneset +fIFS=$old_ifsif [ -n "$stuck" ]; thenprintf 'tmux still answers on%s after kill-server; kept %s and %s under the runner root\n' \"$stuck" "${directory##*/}" "${tmpdir##*/}" >&2exit 1firm -rf "$directory" "$tmpdir" || exit 1if [ -n "$left" ]; thenprintf 'The program left a tmux server running:%s\n' "$left" >&2exit 1fiexit "$status"}trap cleanup 0trap 'exit 1' HUP INT TERM PIPE# A default socket path longer than this does not fit in sun_path.default_socket="$tmpdir/tmux-$(id -u)/default"if [ "$(printf '%s' "$default_socket" | wc -c)" -gt 100 ]; thenprintf 'API_RUN_ROOT is too long for a tmux socket\n' >&2exit 1fiTMUX_TMPDIR=$tmpdirexport TMUX_TMPDIRprintf 'set-option -g default-shell /bin/sh\n' > "$config""$binary" -S "$socket" -f "$config" new-session -d -s work-one -n editor /bin/cat"$binary" -S "$socket" split-window -h -t '=work-one:editor' /bin/cat"$binary" -S "$socket" new-window -d -t '=work-one' -n logs /bin/cat"$binary" -S "$socket" new-session -d -s work-two -n editor /bin/cat"$@" "$binary" "$socket" "$config""$binary" -S "$socket" has-session -t '=work-one'"$binary" -S "$socket" has-session -t '=work-two'Save the complete program as
src/main/clojure/libtmux/examples/api/list_panes.clj, creating its parent directories. Closing its client leaves the launcher's server running. View source(ns libtmux.examples.api.list-panes"Traverse session, window and pane observations without another acquisition."(:require [libtmux.core :as tm][libtmux.data :as data]))(defn -main [& args](when-not (= 3 (count args))(throw (IllegalArgumentException. "expected: tmux-binary socket-path config-file")))(let [[binary socket config] args](with-open [server (tm/open! {:binary binary :socket-path socket:config-file config})](let [snapshot (tm/snapshot! server)session (data/exactly-one(filter #(= "work-one" (:session/name %))(:tmux/sessions snapshot)))window (data/exactly-one(filter #(= "editor" (:window/name %)) (data/windows session)))](println (str "panes=" (count (data/panes snapshot))))(println (str "window-panes=" (count (data/panes window))))))))Run it with the private fixture. Set TMUX_BIN to select another tmux binary:
Terminal window $ sh run.sh clojure \-J-Dclojure.main.report=stderr \-M \-m libtmux.examples.api.list-panesExpected output:
panes=4window-panes=2
tmux command#
The tmux commands behind snapshot!.
list-clients list-panes -a server scope, cmd_list_panes_server list-sessions list-windows -a server scope, cmd_list_windows_exec tmux C API#
The C declarations in tmux that implement these commands.
Guides#
Pages that discuss snapshot!.
- Concepts
In other ports#
Capture the tmux hierarchy
| Language | Equivalent |
|---|---|
| Python | no equivalent on Server |
| Ruby | Lib |
| Lua | libtmux |
| PowerShell | Lib Acquires a replacement LibTmux.Server to -Depth (panes by default). Acquisition is not atomic; retained handles do not refresh. |
| Julia | Lib Acquires a new Snapshot over an observation interval. Topology checks do not make it a transaction; earlier snapshots do not refresh. |
| Erlang | tmux Captures a new graph over an observation interval. Retained snapshots never refresh. |
| Elixir | Lib Captures a new graph over an observation interval. A retained Snapshot never refreshes. |
| Haskell | No public Haskell equivalent is implemented; the current modules expose scalar values and version parsing. |
| Common Lisp | libtmux: fetch-snapshot acquires a new interval observation. Retained snapshots never refresh in place. |
| TypeScript | server |
| Rust | server Fetches the whole hierarchy with list-sessions, list-windows -a and list-panes -a, and stitches the rows by winlink. The three listings are separate commands, so the capture is not an atomic transaction. |
| Go | tmux |
| Java | io |
| Kotlin | io |
| Scala | io |
| C# | Lib |
| F# | Lib Acquires a replacement shared .NET LibTmux.Server to the requested depth. Acquisition is not atomic; retained handles do not refresh. |
| C++ | no equivalent on Server: sessions, windows, panes and clients each take a snapshot of one listing, and Snapshot::take needs the internal detail::Backend, so no public call reads the whole hierarchy at once |
| Swift | Server |
| OCaml | Libtmux Acquires a complete immutable graph over an interval. Earlier observations remain readable without refreshing. |
| Perl | Lib Strict acquisition observes an interval, not an atomic transaction; retained handles do not refresh. |