aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLena <lena@omega>2026-07-01 00:00:00 +0000
committerLena <lena@omega>2026-07-01 00:00:00 +0000
commit3b6025fc0ad913163e3b85d6aad5fe0a3d094a82 (patch)
tree6b3a2ec34c621eed458e3fd39e1caadcbe817426
downloadtilemux-master.tar.gz
Enter tilemuxHEADmaster
An i3-like tiling layer for tmux in a single config file. No plugins, no helper scripts, no dependencies. Design target is tmux 3.6. Windows are workspaces and panes are tiles. tmux exposes only pane geometry to its formats, so the bindings work to one explicit model instead: a window is a row of columns, each column a stack of panes. Every binding is on Alt, so no prefix is needed. README covers install, the model, debugging and the caveats.
-rw-r--r--LICENSE28
-rw-r--r--README137
-rw-r--r--tilemux.conf408
3 files changed, 573 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2f0bd1a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2026, Lena
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
index 0000000..b4cb829
--- /dev/null
+++ b/README
@@ -0,0 +1,137 @@
+tilemux
+=======
+An i3-like tiling layer for tmux, in a single config file. No plugins, no
+helper scripts, no dependencies. Design target is tmux 3.6.
+
+Windows are workspaces, panes are tiles, and every binding is on Alt so
+no prefix is needed. Ctrl+Space is kept as the tmux prefix so nothing is
+out of reach.
+
+
+Install
+-------
+ cp tilemux.conf ~/.tilemux.conf
+
+Then add an alias to your shell rc:
+
+ alias tilemux='tmux -L tilemux -f ~/.tilemux.conf'
+
+ tilemux new -A -s main start it, or attach if already running
+ tilemux ls list sessions
+ tilemux kill-server stop it
+
+tilemux runs on its own socket, so it has its own server and its own
+sessions and the tmux you already use is untouched. The socket is what
+does the isolating: -f is read only when a server starts, so on its own
+it would be ignored by an already running tmux.
+
+Alt+? lists every key. The rules are commented in tilemux.conf next to
+the bindings that implement them, so this file only shows what they do.
+
+
+The model
+---------
+tmux keeps a layout as a tree but exposes only pane geometry to its
+formats, so the bindings work to one explicit model instead:
+
+ a window is a row of columns, and each column is a stack of panes
+
+Alt+Shift+Left/Right moves a pane between columns. It leaves its own
+column and joins the neighbouring one:
+
+ +----+----+ +----+----+
+ | a | C | | | a |
+ +----+----+ -> | +----+
+ | B | D | | B | C |
+ +----+----+ | +----+
+ | | D |
+ +----+----+
+
+Alt+Shift+Up/Down reorders a pane inside the column it is already in:
+
+ +----+----+ +----+----+
+ | a | C | | B | C |
+ +----+----+ -> +----+----+
+ | B | D | | a | D |
+ +----+----+ +----+----+
+
+At the edge of the window there is no neighbour to move to, so the pane
+leaves its column and becomes a column of its own, or a full-width row
+going up or down. This is how a layout is restructured without presets:
+
+ +----+----+ +--+--+----+
+ | A | C | |A | | |
+ +----+----+ -> +--+ C| d |
+ | B | d | |B | | |
+ +----+----+ +--+--+----+
+
+Alt+m and a direction is the way back in. Moving pushes panes apart, and
+a pane that is a whole column swaps with its neighbours instead of
+joining them, so merging needs a key of its own:
+
+ +---+---+---+ +---+-------+
+ | | | | | | B |
+ | A | B | c | -> | A +-------+
+ | | | | | | c |
+ +---+---+---+ +---+-------+
+
+
+Debugging
+---------
+Reload, then look at what a key runs and what the rules are reading:
+
+ tilemux source-file ~/.tilemux.conf
+ tilemux list-keys -T root | grep 'M-L'
+ tilemux display -p '#{window_layout}'
+ tilemux list-panes -F '#{pane_index} #{pane_left},#{pane_top}'
+
+Watch for #{>:a,b}: it compares strings. Numbers need #{e|>:a,b}.
+
+To try a binding without disturbing the tmux you work in, run a second
+server on its own socket and attach to it from a pane of the first.
+send-keys writes to that pane, so the attached client reads the keys and
+the binding fires:
+
+ tmux -L test -f ./tilemux.conf new-session -d -s w -x 200 -y 48
+ tmux -L ctl -f /dev/null new-session -d -x 200 -y 49 \
+ 'tmux -L test attach -t w'
+ tmux -L test split-window -h
+ tmux -L test select-pane -t 1
+ tmux -L ctl send-keys -t 0 M-L
+ tmux -L test list-panes -F '#{pane_index} #{pane_left} #{pane_active}'
+ tmux -L test kill-server
+
+Killing the test server ends the attach, and the ctl server with it.
+
+
+Caveats
+-------
+A 2x2 built as columns of rows and one built as rows of columns look
+identical on screen. Geometry cannot tell them apart, so both behave as
+columns of rows.
+
+A pane that spans the window swaps with a neighbour instead of pushing
+past it, and tmux picks which neighbour by geometry, so a full-height
+column can trade places with one pane of the stack beside it. Alt+m
+puts it back.
+
+A pane leaving a stack does not make the rest rebalance; the neighbour
+absorbs the space, as it does when a pane is killed. Alt+r then = evens
+a container out.
+
+Copy mode has its own key table. With the default emacs mode-keys it
+takes seventeen of these keys for itself: Alt+1 to Alt+9, Alt+Up,
+Alt+Down, Alt+b, Alt+f, Alt+l, Alt+m, Alt+r and Alt+v. Those do not
+reach tilemux while a pane is in copy mode, and every other key falls
+through and works. setw -g mode-keys vi avoids the clash entirely.
+
+Alt+? needs a terminal of at least 76x23. Below that tmux refuses the
+popup and says so in the status line; there is no smaller help.
+
+Alt+Shift with a number assumes the shifted symbols of a US layout
+(!@#$%^&*()).
+
+
+License
+-------
+BSD 3-Clause. See LICENSE.
diff --git a/tilemux.conf b/tilemux.conf
new file mode 100644
index 0000000..5969013
--- /dev/null
+++ b/tilemux.conf
@@ -0,0 +1,408 @@
+# ~/.tilemux.conf - i3-like tiling for tmux
+# Tested design target: tmux 3.6
+#
+# Main modifier: Alt (tmux calls it Meta, written M-).
+# These bindings do NOT require the normal tmux prefix.
+#
+# Layout model: a window is a row of columns, each column a stack of panes.
+# Alt+Shift+left/right move the pane to the neighbouring column
+# Alt+Shift+up/down move the pane within its own column
+# at the window edge the pane leaves its column and becomes a
+# full-width row or full-height column of its own
+# Alt+m then direction merge the pane into the neighbour, the way back
+#
+# Quick help: Alt+?
+
+##### General ###############################################################
+
+# Treat tmux windows like numbered workspaces.
+set -g base-index 1
+setw -g pane-base-index 1
+set -g renumber-windows off
+
+# Put status at the top.
+set -g status-position top
+
+# Alt must not be read as Escape then a key. This is the default on 3.6;
+# setting it explicitly covers older tmux.
+set -sg escape-time 10
+
+# Keep a normal tmux prefix as an escape hatch.
+unbind C-b
+set -g prefix C-Space
+bind C-Space send-prefix
+
+set -g history-limit 50000
+set -g focus-events on
+
+
+##### Help: Alt+? ###########################################################
+# Shift+/ produces ?, so Alt+? is M-? in tmux notation.
+
+bind -n 'M-?' display-popup -E -w 76 -h 23 -T ' tilemux - Enter to close ' 'printf "%s\n" \
+ " WORKSPACES" \
+ " Alt+1..9,0 select/create workspace (0 is workspace 10)" \
+ " Alt+Shift+1..9,0 move pane there; a lone pane takes its window" \
+ " Ctrl+Alt+Left/Right previous/next workspace" \
+ " Alt+Tab, Alt+backtick last workspace" \
+ " Alt+n rename workspace" \
+ " FOCUS" \
+ " Alt+h/j/k/l or Arrow focus left/down/up/right" \
+ " Alt+o last pane" \
+ " MOVE PANE" \
+ " Alt+Shift+h/j/k/l/Arrow left/right to the next column, up/down in it" \
+ " at the window edge: a full row or column" \
+ " Alt+m then a direction merge into the neighbour, the way back" \
+ " PANES" \
+ " Alt+Enter new pane, split along the longer side" \
+ " Alt+b / Alt+v split left|right / top|bottom" \
+ " Alt+f / Alt+Shift+q fullscreen/zoom / kill pane" \
+ " Alt+e cycle tmux preset layouts" \
+ " Alt+r resize mode: hjkl/arrows, = evens, Enter/Esc" \
+ " Ctrl+Space normal tmux prefix (Alt+? shows this list)"; read _'
+
+
+##### Focus panes: Alt+hjkl AND Alt+arrows ##################################
+
+bind -n M-h select-pane -L
+bind -n M-j select-pane -D
+bind -n M-k select-pane -U
+bind -n M-l select-pane -R
+
+bind -n M-Left select-pane -L
+bind -n M-Down select-pane -D
+bind -n M-Up select-pane -U
+bind -n M-Right select-pane -R
+
+# Back to the pane focused before this one.
+bind -n M-o last-pane
+
+
+##### Move panes: Alt+Shift+hjkl AND Alt+Shift+arrows #######################
+#
+# tmux exposes pane geometry to formats but not its layout tree, so the
+# rules are expressed in geometry:
+#
+# left/right pane is as tall as the window -> it is a whole column, so
+# swap it with the neighbour
+# otherwise -> leave this column and join
+# the neighbouring one
+# nothing in that direction -> become a full-height column
+# at that edge of the window
+#
+# up/down a pane in that direction -> swap with it
+# nothing in that direction -> become a full-width row at
+# that edge of the window
+#
+# A pane that already spans the window along the relevant axis does nothing.
+# That is what stops repeated presses from nesting forever.
+#
+# Notes:
+# - {left-of} and friends WRAP AROUND the window, so every use of them is
+# guarded by the matching pane_at_* flag.
+# - tmux takes the source of a join or swap from the marked pane whenever
+# one is set, whatever -s says, so the mark is cleared first, even when
+# the rules below then decide to do nothing. Marks belong to the join
+# and swap workflow these bindings replace.
+# - the pane joins above the neighbour, or below it when moving out of the
+# bottom row, so it keeps roughly the row it came from.
+# - select-layout -E gives every pane in the destination column an equal
+# share, the way i3 does when a window joins a container.
+# - the logic lives in command aliases because tmux cannot bind two keys
+# to one command list any other way. Aliases take no arguments.
+# - a zoomed pane covers the window, so every rule would read as "already
+# spans everything" and do nothing. Moving unzooms first, which is what
+# select-pane does for focus anyway.
+
+set -s command-alias[100] "tile-left=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_left}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_height},#{window_height}}}' { \
+ join-pane -h -f -b -t '{next}' ; select-layout -E \
+ } \
+} { \
+ if -F '#{==:#{pane_height},#{window_height}}' { \
+ swap-pane -d -t '{left-of}' \
+ } { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{left-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{left-of}' ; select-layout -E \
+ } \
+ } \
+}"
+
+set -s command-alias[101] "tile-right=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_right}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_height},#{window_height}}}' { \
+ join-pane -h -f -t '{next}' ; select-layout -E \
+ } \
+} { \
+ if -F '#{==:#{pane_height},#{window_height}}' { \
+ swap-pane -d -t '{right-of}' \
+ } { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{right-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{right-of}' ; select-layout -E \
+ } \
+ } \
+}"
+
+set -s command-alias[102] "tile-up=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_top}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_width},#{window_width}}}' { \
+ join-pane -v -f -b -t '{next}' ; select-layout -E \
+ } \
+} { \
+ swap-pane -d -t '{up-of}' \
+}"
+
+set -s command-alias[103] "tile-down=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_bottom}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_width},#{window_width}}}' { \
+ join-pane -v -f -t '{next}' ; select-layout -E \
+ } \
+} { \
+ swap-pane -d -t '{down-of}' \
+}"
+
+bind -n M-H tile-left
+bind -n M-J tile-down
+bind -n M-K tile-up
+bind -n M-L tile-right
+
+bind -n M-S-Left tile-left
+bind -n M-S-Down tile-down
+bind -n M-S-Up tile-up
+bind -n M-S-Right tile-right
+
+
+##### Merge into the neighbour: Alt+m, then a direction #####################
+#
+# Alt+Shift+direction pushes a pane out of its column, and once a pane is a
+# whole column of its own it swaps with its neighbours instead of joining
+# them. This is the way back in: merge always joins the neighbouring pane,
+# whatever the shape of either of them.
+#
+# Alt+m h/l stack into the column to the left/right
+# Alt+m j/k sit beside the pane below/above, turning a stack into a row
+#
+# Nothing happens at the window edge, because there is no neighbour there.
+# The key table reverts to root after one key, so this is a single chord,
+# not a mode that has to be left.
+
+set -s command-alias[104] "tile-merge-left=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_left},0}' { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{left-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{left-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[105] "tile-merge-right=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_right},0}' { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{right-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{right-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[106] "tile-merge-up=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_top},0}' { \
+ if -F '#{pane_at_right}' { \
+ join-pane -h -t '{up-of}' ; select-layout -E \
+ } { \
+ join-pane -h -b -t '{up-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[107] "tile-merge-down=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_bottom},0}' { \
+ if -F '#{pane_at_right}' { \
+ join-pane -h -t '{down-of}' ; select-layout -E \
+ } { \
+ join-pane -h -b -t '{down-of}' ; select-layout -E \
+ } \
+}"
+
+bind -n M-m switch-client -T merge
+
+bind -T merge h tile-merge-left
+bind -T merge j tile-merge-down
+bind -T merge k tile-merge-up
+bind -T merge l tile-merge-right
+
+bind -T merge Left tile-merge-left
+bind -T merge Down tile-merge-down
+bind -T merge Up tile-merge-up
+bind -T merge Right tile-merge-right
+
+bind -T merge Enter switch-client -T root
+bind -T merge Escape switch-client -T root
+
+
+##### Create panes / layout #################################################
+
+# Alt+Enter: new pane, split along the longer side of the current pane.
+# Terminal cells are about twice as tall as they are wide, hence the *2.
+bind -n M-Enter if -F '#{e|>:#{pane_width},#{e|*:#{pane_height},2}}' {
+ split-window -h -c '#{pane_current_path}'
+} {
+ split-window -v -c '#{pane_current_path}'
+}
+
+# Explicit split orientation.
+bind -n M-b split-window -h -c '#{pane_current_path}'
+bind -n M-v split-window -v -c '#{pane_current_path}'
+
+bind -n M-e next-layout
+bind -n M-f resize-pane -Z
+
+# Shift+q arrives as Q.
+bind -n M-Q kill-pane
+
+
+##### Resize mode: Alt+r, then hjkl OR arrows ###############################
+#
+# The mode stays active until Enter or Escape. Any other key leaves it too,
+# because unbound keys fall back to the root table. status-right names the
+# key table whenever it is not root, so the mode is visible.
+
+bind -n M-r switch-client -T resize
+
+bind -T resize h resize-pane -L 5 \; switch-client -T resize
+bind -T resize j resize-pane -D 2 \; switch-client -T resize
+bind -T resize k resize-pane -U 2 \; switch-client -T resize
+bind -T resize l resize-pane -R 5 \; switch-client -T resize
+
+bind -T resize Left resize-pane -L 5 \; switch-client -T resize
+bind -T resize Down resize-pane -D 2 \; switch-client -T resize
+bind -T resize Up resize-pane -U 2 \; switch-client -T resize
+bind -T resize Right resize-pane -R 5 \; switch-client -T resize
+
+# Give the panes around this one equal shares, without leaving the mode.
+bind -T resize = select-layout -E \; switch-client -T resize
+
+bind -T resize Enter switch-client -T root
+bind -T resize Escape switch-client -T root
+
+
+##### Workspaces ############################################################
+#
+# ":N" means window N in the current session. Alt+0 is workspace 10, so the
+# workspaces read 1..10 in order. list-panes is only a probe for "does this
+# window exist", so its output and its error are both discarded. It runs
+# the tmux binary, so tmux has to be on PATH in the server environment;
+# without it, selecting a workspace that exists fails with "index in use".
+# Command aliases cannot help here: tmux does not expand formats in -t.
+
+bind -n M-1 if-shell 'tmux list-panes -t :1 >/dev/null 2>&1' { select-window -t :1 } { new-window -t :1 -c '#{pane_current_path}' }
+bind -n M-2 if-shell 'tmux list-panes -t :2 >/dev/null 2>&1' { select-window -t :2 } { new-window -t :2 -c '#{pane_current_path}' }
+bind -n M-3 if-shell 'tmux list-panes -t :3 >/dev/null 2>&1' { select-window -t :3 } { new-window -t :3 -c '#{pane_current_path}' }
+bind -n M-4 if-shell 'tmux list-panes -t :4 >/dev/null 2>&1' { select-window -t :4 } { new-window -t :4 -c '#{pane_current_path}' }
+bind -n M-5 if-shell 'tmux list-panes -t :5 >/dev/null 2>&1' { select-window -t :5 } { new-window -t :5 -c '#{pane_current_path}' }
+bind -n M-6 if-shell 'tmux list-panes -t :6 >/dev/null 2>&1' { select-window -t :6 } { new-window -t :6 -c '#{pane_current_path}' }
+bind -n M-7 if-shell 'tmux list-panes -t :7 >/dev/null 2>&1' { select-window -t :7 } { new-window -t :7 -c '#{pane_current_path}' }
+bind -n M-8 if-shell 'tmux list-panes -t :8 >/dev/null 2>&1' { select-window -t :8 } { new-window -t :8 -c '#{pane_current_path}' }
+bind -n M-9 if-shell 'tmux list-panes -t :9 >/dev/null 2>&1' { select-window -t :9 } { new-window -t :9 -c '#{pane_current_path}' }
+bind -n M-0 if-shell 'tmux list-panes -t :10 >/dev/null 2>&1' { select-window -t :10 } { new-window -t :10 -c '#{pane_current_path}' }
+
+bind -n C-M-Left previous-window
+bind -n C-M-Right next-window
+
+# Back to the workspace used before this one. Alt+backtick is there because
+# desktops that are not i3 tend to swallow Alt+Tab before the terminal sees
+# it.
+bind -n M-Tab last-window
+bind -n 'M-`' last-window
+
+# %%% substitutes the answer with " \ $ ; ~ escaped, so a name cannot end
+# the command and start another one, and -- keeps a leading dash a name.
+# Two costs: a backslash in a name arrives doubled, and tmux expands #{}
+# and #() in the name itself, which no quoting here can prevent.
+bind -n M-n command-prompt -p 'rename workspace:' 'rename-window -- "%%%"'
+
+
+##### Alt+Shift+number = move pane to workspace #############################
+#
+# Shifted number encoding:
+# 1 ! 2 @ 3 # 4 $ 5 % 6 ^ 7 & 8 * 9 ( 0 )
+#
+# Behaviour:
+# - if the destination exists: join the pane there with -d, so this
+# workspace stays selected and focus falls to a pane that remains here;
+# - if it does not exist and this window has other panes: break the pane
+# into window N with -d, again staying here;
+# - if this is the only pane in the window, the whole window has to move,
+# because no pane would be left here to stay focused on. That does
+# follow the window to workspace N.
+
+bind -n 'M-!' if -F '#{!=:#{window_index},1}' {
+ if-shell 'tmux list-panes -t :1 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :1 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :1 } { break-pane -d -t :1 }
+ }
+}
+bind -n 'M-@' if -F '#{!=:#{window_index},2}' {
+ if-shell 'tmux list-panes -t :2 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :2 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :2 } { break-pane -d -t :2 }
+ }
+}
+bind -n 'M-#' if -F '#{!=:#{window_index},3}' {
+ if-shell 'tmux list-panes -t :3 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :3 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :3 } { break-pane -d -t :3 }
+ }
+}
+bind -n 'M-$' if -F '#{!=:#{window_index},4}' {
+ if-shell 'tmux list-panes -t :4 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :4 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :4 } { break-pane -d -t :4 }
+ }
+}
+bind -n 'M-%' if -F '#{!=:#{window_index},5}' {
+ if-shell 'tmux list-panes -t :5 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :5 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :5 } { break-pane -d -t :5 }
+ }
+}
+bind -n 'M-^' if -F '#{!=:#{window_index},6}' {
+ if-shell 'tmux list-panes -t :6 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :6 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :6 } { break-pane -d -t :6 }
+ }
+}
+bind -n 'M-&' if -F '#{!=:#{window_index},7}' {
+ if-shell 'tmux list-panes -t :7 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :7 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :7 } { break-pane -d -t :7 }
+ }
+}
+bind -n 'M-*' if -F '#{!=:#{window_index},8}' {
+ if-shell 'tmux list-panes -t :8 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :8 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :8 } { break-pane -d -t :8 }
+ }
+}
+bind -n 'M-(' if -F '#{!=:#{window_index},9}' {
+ if-shell 'tmux list-panes -t :9 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :9 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :9 } { break-pane -d -t :9 }
+ }
+}
+bind -n 'M-)' if -F '#{!=:#{window_index},10}' {
+ if-shell 'tmux list-panes -t :10 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :10 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :10 } { break-pane -d -t :10 }
+ }
+}
+
+
+##### Status bar ############################################################
+# %Y etc are expanded by tmux itself, so the status line forks nothing.
+
+set -g status-left '[#S] '
+set -g status-right '#{?#{==:#{client_key_table},root},,[#{client_key_table}] }%Y-%m-%d %H:%M'
+set -g window-status-format ' #I:#W '
+set -g window-status-current-format '[#I:#W#{?window_zoomed_flag, Z,}]'