Read Post As PDF

Background

Circa 2003 I had only one computer. I could only aford one, so I only had one. And even if I had more, I wouldn’t know what to do with them. I dabbled in various Linux distributions, and later OpenSolaris and Illumos. Sometime in college, in Chicago, I got a second computer (a laptop to complement my desktop). That presented all kinds of difficulties with syncing. Primarily because SSH conections between computers on the university network were laggy and unreliable. We used to joke that syncing ZFS datasets over a USB thumb-drive had better throughput and latency. Finally, when SmartOS wouldn’t run on neither my desktop nor my laptop, I got a pretty big used server that ran in NYC, and the SSH connection wasn’t a problem.

From the very beginning I was a fanatically enthusiastic (and outspoken) adopter of two things: the terminal and the tiling window manager (i.e. Xmonad, i3, dwm). These two tools complemented each other very well. My typical usage of the window-manager is that terminals get tiled, while graphical things like browsers and document viewers are used in full-screen mode. However, with my adoption of the client-server model, some problems materialized. Firstly, some client-side OSes don’t have a particular window-manager, or an up-to-date version of my window manager of choice. This meant that, sometimes, switches to a new OS, necessitate a switch between window-managers, and the use of older releases. Second, the window manager might have awkward default parameters, which I’d have to configure. Third, some components would just not be installed, or wouldn’t work for some reason. Fourth, some OSes compile the WM to look for the configuration files in a very non-obvious, unlikely place — forcing me to figure out where to put the config, before I can get it working. Finally, and most annoyingly, if I lose internet connectivity, all of the ssh-sessions close, making it necessary for me to login again, and to do a dance where I chdir to the previous project directories, and re-open all of my files for editing, and so forth.

I’ve had to deal with this juggling of the software and configs enough times, when I switch client OSes, that it’s become something of a productivity bottle-neck. X11 is extremely customizable, but this customization gets tedious after a few times. And if you have more than 1 client (I also have a Mac), it becomes very tiring having to move between a flavor of tiling management on one laptop, and the Mac’s floating management on the other. Mac’s Terminal tabs are ok, but they just don’t get the job done efficiently enough.

Tmux

The good news is that the tiling of terminals can be moved from the client to the server. The two most popular pieces of software for handling this are: tmux and screen. I use tmux because it came highly recommended from my colleague.

By using tmux, you effectively centralize the tiling of terminals, making this functionality available on any client-device that has the ability to establish SSH connections. Tmux is scriptable from the command-line. This means that you can automate the creation of new sessions and new terminals. You can also send key-strokes to terminals, via script. This makes it possible for tmux to — for example — create a session for each project, and open a project’s files in an editor, and automatically put you in the build-directory and run a preliminary make all — upon a single login over SSH. I call this “jump-starting”, but that’s just me. This kind of automation is useful in both a development and administration setting.

Tmux’s defaults, however, are not really sensible. So I’ll walk you through installing it, reconfiguring it, and setting up a “jump-start” script. Also, while, tmux can be installed and run from the global zone, it is preferable that it is installed and configured in a non-global zone.

The Install

To install tmux on SmartOS, simply do the following:

pfexec pkgin in tmux

That’s all there is to it. You can launch it from the command-line by typing tmux . You’ll see that tmux has launched a new shell, and drawn a status-bar on the bottom of the terminal. If you hit Ctrl-b d you’ll get dumped back to the original terminal which spawned tmux. If you type tmux ls , you’ll see that the session is still active. You can re-attach to that session by executing tmux attach -t 0 .

So that’s pretty nifty. You don’t have to worry about losing connectivity. If fact, more than 1 computer can attach to the same session. This means that people can collaboratively edit code, for example. Tmux has terrible default bindings, that I won’t expand on here. We will redefine the bindings in the .tmux.conf file, and continue from there.

The Prefix

Before we redefine tmux’s bindings, we should redefine the client-device’s CAPSLOCK key to be interpreted as a Ctrl key. On X11, you want to execute the following:

setxkbmap -option ctrl:nocaps

On OS X, you want to do the following:

Apple Menu -> System Preferences -> Keyboard -> Keyboard Tab -> Modifier Keys

Select the dropdown box next to Caps Lock , and choose Control .

The Config

Tmux expects to find a config file in ~/.tmux.conf . If it doesn’t it will use the defaults. The config file can change the tmux colors and key bindings. Here is the first section of my config file:

# We want to set the prefix to C-a. C-b is kind of awkward. C-a works well if # CAPSLOCK is remapped to Ctrl. set -g prefix C-a unbind C-b # We want to imitate WMs like i3 and allow tmux to reload its config in a few # keystrokes. bind r source-file ~/.tmux.conf # We never want to use the mouse. setw -g mode-mouse off # If we don't set this tmux screws up the appearance of vim's syntax # highlighting. set -g default-terminal "screen-256color"

We set the prefix to Ctrl-a . This way we can enter tmux’s command mode while keeping our hands on the home row.

We bind r to the tmux command source-file ~/.tmux.conf . Pressing C-a r will automatically reload the config and update tmux. We also disable the mouse mode because no self-respecting programmer should use the mouse.

We tell tmux that the terminal has 256 colors supported.

On the client-side you’ll want to make sure that your terminal emulator is configured to show 256 colors and the UTF8 character set. If the UTF8 support is disabled, you’ll see weird characters every time you split a window into a pane, for example — more on that later.

You can also reset the colors. The default colors aren’t really my cup of tea. I use Solarized everywhere I can. Everyone I’ve spoken to, either loves solarized or hates solarized. Clearly it’s a polarizing color scheme. I’m not advocating for solarized here, just showing which colors you can change.

## We set the tmux colors to Solarized ## Lifted from https://github.com/seebi/tmux-colors-solarized # default statusbar colors set-option -g status-bg colour235 #base02 set-option -g status-fg colour130 #yellow set-option -g status-attr default # default window title colors set-window-option -g window-status-fg colour33 #base0 set-window-option -g window-status-bg default #set-window-option -g window-status-attr dim # active window title colors set-window-option -g window-status-current-fg colour196 #orange set-window-option -g window-status-current-bg default #set-window-option -g window-status-current-attr bright # pane border set-option -g pane-border-fg colour235 #base02 set-option -g pane-active-border-fg colour46 #base01 # message text set-option -g message-bg colour235 #base02 set-option -g message-fg colour196 #orange # pane number display set-option -g display-panes-active-colour colour20 #blue set-option -g display-panes-colour colour196 #orange # clock set-window-option -g clock-mode-colour colour40 #green # Center the status bar set -g status-justify centre # Highlight windows that have activity on them setw -g monitor-activity on set -g visual-activity on ## End of visual settings

Tmux can split a window in half either horizontally or vertically. The default keys require you to hold down Shift . That’s a drag, so we rebind it to lower-case keys:

# Some faster keys for window-splitting bind = split-window -h bind - split-window -v

A window is just a box that has panes. Every window starts with 1 pane which has a shell running in it. Splitting a window vertically by hitting C-a - will cut the current pane in half horizontally and create a second underneath the first pane.

Splitting the window horizontally, by hitting C-a = , will split the current pane in half vertically, and create a second pane next to the current one. Obviously the terms horizontal and vertical don’t refer to the direction of the cut but to the direction in which a new pane is appended to window.

We can resize panes and move between them using the keyboard. Unfortunately, the keys for this are cumbersome to use, especially since I — as a long time vim user — instictively use the vim movement keys. Here is the section of the config that maps movement to the vim movement keys:

# Vim movement keys instead of arrows bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # Vim movement key for resizing bind -r H resize-pane -L 5 bind -r J resize-pane -D 5 bind -r K resize-pane -U 5 bind -r L resize-pane -R 5

Sessions, Windows, Panes

In tmux the unit of currency is the session. The session is what you can attach to and detach from. Each session has at least one window, and each window has at least one pane. Windows are akin to what X11 calls “workspaces”, and panes are kind of like what X11 calls “windows”.

The session is an awesome concept. There is no limit to the number of sessions you can have, while there is a limit to the number of windows you can have (10 — more than that becomes cumbersome to manage), and the number of panes you can have (your screen-size isn’t infinite).

The session is a very useful way to organize your work. For development, I open 1 session for each of my active projects. Within the session, I open a bunch of windows, such as “source”, “build”, and “git”. Within the windows, I open up to four panes. So in the “source” window, I have 4 vim instances running, each opened to a distinct source-file.

Now you may be thinking that opening a distinct session per project, and a window per task, and pane per sub-unit of work, is kind of tedious to set up. Well, it is. And having to do this every time is a major pain in the neck. The best thing about tmux is that it can be controlled from the command-line using the tmux command. You can tell tmux to open a session, name the session after a project, and to open some named windows. You can then tell tmux to send keystrokes to the session’s windows and panes. Effectively, tmux can set up a session with all of the appropriate files opened in their windows and panes.

You can roll this sequence commands into a shell script, and execute it to automatically generate a development session. I personally, put this sequence of commands into my .kshrc file, so that the sessions get opened the first time I log in. This effectively jump-starts my development, after I reboot the zone or the server.

Jumpstarting

Here is a sample of what tmux can do. In this example, we will use the the smartos-live repository. Here is the smartos-live section of the .kshrc file. There are probably better ways to jump-start your sessions, but at the moment I am using .kshrc to facilitate this. I’ll post an update if I figure out something more elegant — for example maybe an SMF service that does this for you.

sleep 10 session="smartos-live" # Put your path to smartos-live here, instead prjprefix="/depot/synthesis/smartos-live" tmux has-session -t $session if [[ $? != 0 ]]; then # create the window for editing source code tmux new-session -s $session -n source -d # split the window into quadrants tmux split-window -h -t $session:0 tmux split-window -v -t $session:0.0 tmux split-window -v -t $session:0.1 # chdir into the source directory tmux send-keys -t $session:0.0 "cd $prjprefix/src/" C-m tmux send-keys -t $session:0.1 "cd $prjprefix/src/" C-m tmux send-keys -t $session:0.2 "cd $prjprefix/src/" C-m tmux send-keys -t $session:0.3 "cd $prjprefix/src/" C-m # open vim for files of interest tmux send-keys -t $session:0.0 "vim zoneevent.c" C-m tmux send-keys -t $session:0.1 "vim zfs_recv.c" C-m tmux send-keys -t $session:0.2 "vim zfs_send.c" C-m tmux send-keys -t $session:0.3 "vim vmunbundle.c" C-m # A new window for doing builds tmux new-window -n build -t $session # We want to divide the window into 1 panes. We want as much vertical # space as possible, for the error-log. tmux split-window -h -t $session:1 tmux send-keys -t $session:1.0 "cd $prjprefix/" C-m tmux send-keys -t $session:1.1 "cd $prjprefix/" C-m tmux send-keys -t $session:1.0 "vim configure.local" C-m tmux send-keys -t $session:1.1 "vim Makefile" C-m # A new window for git ops. tmux new-window -n git -t $session tmux split-window -h -t $session:2 tmux split-window -v -t $session:2.0 tmux split-window -v -t $session:2.1 tmux send-keys -t $session:2.0 "cd $prjprefix" C-m tmux send-keys -t $session:2.1 "cd $prjprefix" C-m tmux send-keys -t $session:2.2 "cd $prjprefix" C-m tmux send-keys -t $session:2.3 "cd $prjprefix" C-m tmux send-keys -t $session:2.0 "git status" C-m tmux send-keys -t $session:2.1 "git log" C-m # This file is opened because the README file is a repo's "front page" # or "cover page". tmux send-keys -t $session:2.2 'vim README.md' C-m fi

If you put this in your .kshrc file, log out, and log in, nothing will seem amiss. But if you run tmux ls , you’ll see:

smartos-live: 3 windows (created Sun Mar 15 20:57:29 2015) [212x60]

If you run tmux attach -t smartos-live , you’ll be placed into a session with 3 windows: “source”, “build”, and “git”. The source-window has 4 panes, each with vim open to a specific source-file. The build-window has 2 panes, one has an instance of vim Makefile , while the other has vim configure.local . The git-window, has 4 panes: one in which git status has been run, one in which git log has been run, one in which vim README.md has been run, and a shell that is already in the root of the repo.

To leave the session simply type: C-a :detach . You won’t lose the session, since it’s running in the background. And you can re-attach later. You can use the above KSH code, to create other sessions. For example, you can replace session=... with a new session-name, and the prjprefix=... with a new project-directory. The only thing you have to change inside of the body of the if-block, is the specific file-names of the source-files (since these differ between projects). But if you know that you will always open 4 files in 4 panes, you can abstract that away too with 4 new variables.

You don’t have to worry about creating the same session twice, since the session-creating code is firmly placed within the if-block, and only runs if a session with the given name doesn’t already exist.

Also, in case it’s unclear, the send-keys command’s syntax means the following:

tmux send-keys -t [session-name]:[window-number].[pane-number] [keys] [^m]

We send a Ctrl-m so that a carriage-return is passed to the pane we are targeting.

It is absolutely crucial that you put a sleep 10 before you start executing tmux commands. Remember that this is run from .kshrc , and tmux itself launches instances of KSH. If you don’t force a delay before running the tmux section of the script, you’ll end up with a race-condition, that creates the same windows and panes multiple times in the same session. It is sufficient to do this before creating the first session — no need to this for every session.

Further Reading

This post is far from a comprehensive guide to using Tmux. I strongly suggest that you read the man pages.