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 prefix,key 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 (prefix,d
) 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.