The Power Of tmux Hooks

tmux's prefix key

The default tmux prefix key is Ctrl - b, for the remainder of this article, we will refer to the prefix key as prefix.

The convention prefixkey means pressing prefix and the key after the prefix is released.

tmux's set-hook is my new favorite feature. It is insanely powerful, and opens up the possibilities for a new level of tmux scripting.

tmux hook basics

The basic layout of setting a tmux hook is this:

$ tmux set-hook <hook-name> command

By default, the hook applies to your current session.

Like most other commands, you can set a target with -t, in this case, a session:

$ tmux set-hook -t <session-name> <hook-name> command

If you want your script to be more resilient, you want to specify -t to make sure the hook binds to the correct session.

Let's give this a shot. From within a tmux session, do:

$ tmux set-hook client-attached 'display-message hi'

Now detach your session (prefixd) and reattach your session ($ tmux a). You should see a message in your status-line saying 'hi'.

Let's check on what happened:

$ tmux show-hooks
client-attached -> display-message hi

You can detach, and reattach again, the message shows each time you reattach that session. (We'll talk about making global hooks after this).

To clean up, use -u to unset the hook:

$ tmux set-hook -u client-attached

Global Hooks

For hooks that run across all sessions, add the -g flag (just like when setting options).

$ tmux set-hook -g client-attached 'display-message "hello world"'

Pay attention to the double quotes around "hello world". Without those, the command may break:

$ tmux set-hook -g client-attached 'display-message hi world'
usage: display-message [-p] [-c target-client] [-F format] [-t target-pane] [message]

Reattach your current session or any other session. The message "hello world" should show.

Note, for completely new sessions, you may not see it. Use session-created for that:

$ tmux set-hook -g session-created 'display-message "first time"'

Then open a new terminal, and open tmux with $ tmux , you should see "first time" display in the status line when the client attaches.

Another thing is that $ tmux show-hooks should be blank. There's no session specific hooks since we unset that. To see the global hooks, do this:

$ tmux show-hooks -g
client-attached -> display-message "hi world"
session-created -> display-message "first time"

To clean up, let's unset both hooks with a chained command. tmux allows multiple commands to be separated by semicolons:

$ tmux set-hook -gu client-attached\; set-hook -gu session-created

Pay attention to the escaped semicolon, \;, we don't want to clobber with the shell's semicolon.

Use cases

Hooks are best for those already involved in tmux scripting. It's great for hackers who want to think of clever ways to make their scripts more resilient:

Auto-sizing main-pane layouts

As of tmux 2.6, tmux keeps tiled, even-vertical, and even-horizontal layouts even when resizing. However, users that have main-pane-horizontal and main-pane-vertical must set an option to size the main-pane manually. Percentages are not allowed, making it difficult to stay proportional with varying client dimensions.

If you switch between computers with tmux, external monitors, resolutions, maximize, or through any other mean resize your terminal. main-pane-vertical and main-pane-horizontal layouts require manual resizing.

The issue is two fold: First you need to have a consistent calculation of the pane proportions to resize to. Second, you need it to happen at the time of attaching the client or switching to the session (if already inside a client).

This is taken from my tmux configuration. Let's assume you want horizontal proportions (main pane at the bottom, then splits at the bottom panes). Refer to the script in Resize tmux main panes by percentage or grab resize-adaptable.sh and copy it to ~/.tmux/scripts/resize-adaptable.sh.

Next, we need to set a hook to run this script to set proportions when certain hooks are triggered. Below, let's set a main-horizontal layout with a pane 2/3 of the client's height whenever the client is resized (client-resized):

$ tmux set-hook -g client-resized 'run-shell "~/.tmux/scripts/resize-adaptable.sh -p 66 -l main-horizontal"'

Here is the same for a main-vertical layout set to half the client's width:

$ tmux set-hook -g client-resized 'run-shell "~/.tmux/scripts/resize-adaptable.sh -p 50 -l main-vertical"'

To unset the hook:

$ tmux set-hook -gu client-resized

To set the hook whenever a client is attached, switch out client-resized with client-attached:

$ tmux set-hook -g client-attached 'run-shell "~/.tmux/scripts/resize-adaptable.sh -p 50 -l main-vertical"'

This works across all sessions, remove the -g to set it to your current session. Removing -g and adding -t session-target will set the hook for a session. For instance, a session called "develtech":

$ tmux set-hook -t develtech client-resized 'run-shell "~/.tmux/scripts/resize-adaptable.sh -p 50 -l main-vertical"'

To unset that, do:

$ tmux set-hook -u -t develtech client-resized

If you want this to be in your tmux by default, remove the "tmux" from the beginning and add it to ~/.tmux.conf:

set-hook -g client-attached 'run-shell "~/.tmux/scripts/resize-adaptable.sh -p 50 -l main-vertical"'

tmuxp

In my situation, tmuxp, a tmux session manager, needed to have tmux sessions built in a detached state (via -d). However, in tmux 2.6, the way layouts were handled prior to a client attaching changed, layouts weren't setting correctly. tmuxp, at this point, had no way to defer an automatic command to serve as a "final touch-up" upon attaching a session.

To fix this, I created a hook that'd run $ tmux selectl for the window after the client is attached. See the usage of set_layout_hook() and its implementation.

However, there was still an issue to be solved. Setting a hook would re-run it every time a client was attached or session was switched. This would be annoying to the user. So we needed to devise a way to make a hook that ran only once.

Running a hook once

You can configure you hooks to unset themselves after they run. To do this, use a semicolon and set-hook -u hook-name. Here are some examples:

For hooks ran once within a session:

$ tmux set-hook client-attached 'display-message "hi session"; set-hook -u client-attached'

Detach and reattach your session, "hi session" should show in the display status at the bottom. In addition, $ tmux show-hooks won't show client-attached, it was cleaned up after.

When scripting, you'll probably want to add the -t part, replace session-name with the name of the session. This can be ran inside or ouside the session, as long as it exists:

$ tmux set-hook -t session-name client-attached 'display-message "hi specific"; set-hook -t session-name -u client-attached'

Reattach session-name, you should see "hi specific" in the status line. Typing $ tmux show-hooks from inside session-name shouldn't show client-attached, it was unset. Also, you can use $ tmux show-hooks -t session-name from within and outside of the session.

As long as tmux is already running, this can be ran anywhere.

$ tmux set-hook -g client-attached 'display-message "hi world"; set-hook -gu client-attached'

Reattach any session, "hi world" will show, and $ tmux show-hooks -g won't show the client-attached hook, it automatically cleaned it up.

Finding when tmux hooks trigger

There's a trick you can use to make sure your specific circumstance will trigger a hook and what the hook name is. Grep tmux's files prefixed in cmd-, e.g. cmd-new-session.c and find notify_client. That shows you when tmux triggers the hook and what hook-name is used.

I had an issue with a hook name after-client-switch, which I'd think to exist, following the after- command convention, but that didn't pan out. tmux 2.6 will trigger client-session-changed, so look out for that.

Learn more about tmux

For more tmux tips, check out The Tao of tmux: and Terminal Tricks available to read for free online, and on Amazon Kindle.