288 lines
11 KiB
Plaintext
288 lines
11 KiB
Plaintext
Layout saving in i3
|
||
===================
|
||
Michael Stapelberg <michael@i3wm.org>
|
||
April 2014
|
||
|
||
Layout saving/restoring is a feature that was introduced in i3 v4.8.
|
||
|
||
Layout saving/restoring allows you to load a JSON layout file so that you can
|
||
have a base layout to start working with after powering on your computer.
|
||
Dynamic use-cases also come to mind: if you frequently (but not always!) need a
|
||
grid layout of terminals with ping/traceroute commands to diagnose network
|
||
issues, you can easily automate opening these windows in just the right layout.
|
||
|
||
== Saving the layout
|
||
|
||
You can save the layout of either a single workspace or an entire output (e.g.
|
||
LVDS1). Of course, you can repeat this step multiple times if you want to
|
||
save/restore multiple workspaces/outputs.
|
||
|
||
+i3-save-tree(1)+ is a tool to save the layout. It will print a JSON
|
||
representation of i3’s internal layout data structures to stdout. Typically,
|
||
you may want to take a quick look at the output, then save it to a file and
|
||
tweak it a little bit:
|
||
|
||
---------------------------------------------------
|
||
i3-save-tree --workspace 1 > ~/.i3/workspace-1.json
|
||
---------------------------------------------------
|
||
|
||
Please note that the output of +i3-save-tree(1)+ is *NOT useful* until you
|
||
manually modify it — you need to tell i3 how to match/distinguish windows (for
|
||
example based on their WM_CLASS, title, etc.). By default, all the different
|
||
window properties are included in the output, but commented out. This is partly
|
||
to avoid relying on heuristics and partly to make you aware how i3 works so
|
||
that you can easily solve layout restoring problems.
|
||
|
||
How to modify the file manually is described in <<EditingLayoutFiles>>.
|
||
|
||
== Restoring the layout
|
||
|
||
After restoring the example layout from <<EditingLayoutFiles>>, i3 will open
|
||
placeholder windows for all the windows that were specified in the layout file.
|
||
You can recognize the placeholder windows by the watch symbol
|
||
footnote:[Depending on the font you are using, a placeholder symbol may show up
|
||
instead of the watch symbol.] in the center of the window, and by the swallow
|
||
criteria specification at the top of the window:
|
||
|
||
image:layout-saving-1.png["Restored layout",width=400,link="layout-saving-1.png"]
|
||
|
||
When an application opens a window that matches the specified swallow criteria,
|
||
it will be placed in the corresponding placeholder window. We say it gets
|
||
*swallowed* by the placeholder container, hence the term.
|
||
|
||
Note: Swallowing windows into unsatisfied placeholder windows takes precedence
|
||
over
|
||
link:https://i3wm.org/docs/userguide.html#_automatically_putting_clients_on_specific_workspaces[assignment
|
||
rules]. For example, if you assign all Emacs windows to workspace 1 in your i3
|
||
configuration file, but there is a placeholder window on workspace 2 which
|
||
matches Emacs as well, your newly started Emacs window will end up in the
|
||
placeholder window on workspace 2.
|
||
|
||
The placeholder windows are just regular windows, so feel free to move them
|
||
around or close them, for example.
|
||
|
||
=== append_layout command
|
||
|
||
The +append_layout+ command is used to load a layout file into i3. It accepts a
|
||
path (relative to i3’s current working directory or absolute) to a JSON file.
|
||
|
||
*Syntax*:
|
||
--------------------------------------------------------------------------------
|
||
append_layout <path>
|
||
--------------------------------------------------------------------------------
|
||
|
||
*Examples*:
|
||
--------------------------------------------------------------------------------
|
||
# From a terminal or script:
|
||
i3-msg "workspace 1; append_layout /home/michael/.i3/workspace-1.json"
|
||
|
||
# In your i3 configuration file, you can autostart i3-msg like this:
|
||
# (Note that those lines will quickly become long, so typically you would store
|
||
# them in a script with proper indentation.)
|
||
exec --no-startup-id "i3-msg 'workspace 1; append_layout /home/michael/.i3/workspace-1.json'"
|
||
--------------------------------------------------------------------------------
|
||
|
||
== Editing layout files
|
||
|
||
[[EditingLayoutFiles]]
|
||
|
||
=== Anatomy of a layout file
|
||
|
||
Here is an example layout file that we’ll discuss:
|
||
|
||
--------------------------------------------------------------------------------
|
||
{
|
||
// splitv split container with 2 children
|
||
"layout": "splitv",
|
||
"percent": 0.4,
|
||
"type": "con",
|
||
"nodes": [
|
||
{
|
||
"border": "none",
|
||
"name": "irssi",
|
||
"percent": 0.5,
|
||
"type": "con",
|
||
"swallows": [
|
||
{
|
||
"class": "^URxvt$",
|
||
"instance": "^irssi$"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
// stacked split container with 2 children
|
||
"layout": "stacked",
|
||
"percent": 0.5,
|
||
"type": "con",
|
||
"nodes": [
|
||
{
|
||
"name": "notmuch",
|
||
"percent": 0.5,
|
||
"type": "con",
|
||
"swallows": [
|
||
{
|
||
"class": "^Emacs$",
|
||
"instance": "^notmuch$"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"name": "midna: ~",
|
||
"percent": 0.5,
|
||
"type": "con"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
|
||
{
|
||
// stacked split container with 1 children
|
||
"layout": "stacked",
|
||
"percent": 0.6,
|
||
"type": "con",
|
||
"nodes": [
|
||
{
|
||
"name": "chrome",
|
||
"type": "con",
|
||
"swallows": [
|
||
{
|
||
"class": "^Google-chrome$"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
--------------------------------------------------------------------------------
|
||
|
||
In this layout, the screen is divided into two columns. In the left column,
|
||
which covers 40% of the screen, there is a terminal emulator running irssi on
|
||
the top, and a stacked split container with an Emacs window and a terminal
|
||
emulator on the bottom. In the right column, there is a stacked container with
|
||
a Chrome window:
|
||
|
||
image:layout-saving-1.png["Restored layout",width=400,link="layout-saving-1.png"]
|
||
|
||
The structure of this JSON file looks a lot like the +TREE+ reply, see
|
||
https://build.i3wm.org/docs/ipc.html#_tree_reply for documentation on that. Some
|
||
properties are excluded because they are not relevant when restoring a layout.
|
||
|
||
Most importantly, look at the "swallows" section of each window. This is where
|
||
you need to be more or less specific. As an example, remember the section about
|
||
the Emacs window:
|
||
|
||
--------------------------------------------------------------------------------
|
||
"swallows": [
|
||
{
|
||
"class": "^Emacs$",
|
||
"instance": "^notmuch$"
|
||
}
|
||
]
|
||
--------------------------------------------------------------------------------
|
||
|
||
Here you can see that i3 will require both the class and the instance to match.
|
||
Therefore, if you just start Emacs via dmenu, it will not get swallowed by that
|
||
container. Only if you start Emacs with the proper instance name (+emacs24
|
||
--name notmuch+), it will get swallowed.
|
||
|
||
You can match on "class", "instance", "window_role" and "title". All values are
|
||
case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
|
||
window to see its properties:
|
||
|
||
--------------------------------------------------------------------------------
|
||
$ xprop
|
||
WM_WINDOW_ROLE(STRING) = "gimp-toolbox-color-dialog"
|
||
WM_CLASS(STRING) = "gimp-2.8", "Gimp-2.8"
|
||
_NET_WM_NAME(UTF8_STRING) = "Change Foreground Color"
|
||
--------------------------------------------------------------------------------
|
||
|
||
The first part of +WM_CLASS+ is the "instance" (gimp-2.8 in this case), the
|
||
second part is the "class" (Gimp-2.8 in this case). "title" matches against
|
||
+_NET_WM_NAME+ and "window_role" matches against +WM_WINDOW_ROLE+.
|
||
|
||
In general, you should try to be as specific as possible in your swallow
|
||
criteria. Try to use criteria that match one window and only one window, to
|
||
have a reliable startup procedure.
|
||
|
||
If you specify multiple swallow criteria, the placeholder will be replaced by
|
||
the window which matches any of the criteria. As an example:
|
||
|
||
--------------------------------------------------------------------------------
|
||
// Matches either Emacs or Gvim, whichever one is started first.
|
||
"swallows": [
|
||
{"class": "^Emacs$"},
|
||
{"class": "^Gvim$"}
|
||
]
|
||
--------------------------------------------------------------------------------
|
||
|
||
=== JSON standard non-compliance
|
||
|
||
A layout file as generated by +i3-save-tree(1)+ is not strictly valid JSON:
|
||
|
||
1. Layout files contain multiple “JSON texts” at the top level. The JSON
|
||
standard doesn't prohibit this, but in practice most JSON parsers only
|
||
allow precisely one “text” per document/file, and will mark multiple texts
|
||
as invalid JSON.
|
||
|
||
2. Layout files contain comments which are not allowed by the JSON standard,
|
||
but are understood by many parsers.
|
||
|
||
Both of these deviations from the norm are to make manual editing by humans
|
||
easier. In case you are writing a more elaborate tool for manipulating these
|
||
layouts, you can either use a JSON parser that supports these deviations (for
|
||
example libyajl), transform the layout file to a JSON-conforming file, or
|
||
link:https://github.com/i3/i3/blob/next/.github/CONTRIBUTING.md[submit a patch]
|
||
to make +i3-save-tree(1)+ optionally output standard-conforming JSON.
|
||
|
||
== Troubleshooting
|
||
|
||
=== Restoring a vertically split workspace
|
||
|
||
When using +i3-save-tree+ with the +--workspace+ switch, only the *contents* of
|
||
the workspace will be dumped. This means that properties of the workspace
|
||
itself will be lost.
|
||
|
||
This is relevant for, e.g., a vertically split container as the base container of
|
||
a workspace. Since the split mode is a property of the workspace, it will not be
|
||
stored. In this case, you will have to manually wrap your layout in such a
|
||
container:
|
||
|
||
--------------------------------------------------------------------------------
|
||
// vim:ts=4:sw=4:et
|
||
{
|
||
// this is a manually added container to restore the vertical split
|
||
"layout": "splitv",
|
||
"percent": 0.5,
|
||
"type": "con",
|
||
"nodes": [
|
||
|
||
// the dumped workspace layout goes here
|
||
|
||
]
|
||
}
|
||
--------------------------------------------------------------------------------
|
||
|
||
=== Placeholders using window title matches don't swallow the window
|
||
|
||
If you use the +title+ attribute to match a window and find that it doesn't
|
||
work or only works sometimes, the reason might be that the application sets the
|
||
title only after making the window visible. This will be especially true for
|
||
programs running inside terminal emulators, e.g., +urxvt -e irssi+ when
|
||
matching on +title: "irssi"+.
|
||
|
||
One way to deal with this is to not rely on the title, but instead use, e.g.,
|
||
the +instance+ attribute and running the program to set this window instance to
|
||
that value:
|
||
|
||
--------------------------------------------------------------------------------
|
||
# Run irssi via
|
||
# urxvt -name "irssi-container" -e irssi
|
||
|
||
"swallows": [
|
||
{
|
||
"class": "URxvt",
|
||
"instance": "irssi-container"
|
||
}
|
||
]
|
||
--------------------------------------------------------------------------------
|