-
There are several entry points to rendering: render_layout(),
-render_workspace() and render_container(). The former one calls
-render_workspace() for every screen, which in turn will call
-render_container() for every container inside its layout table. Therefore, if
-you need to render only a single container, for example because a window was
-removed, added or changed its title, you should directly call
-render_container().
-
Rendering consists of two steps: In the first one, in render_workspace(), each
-container gets its position (screen offset + offset in the table) and size
-(container’s width times colspan/rowspan). Then, render_container() is called,
-which takes different approaches, depending on the mode the container is in:
+
Rendering in i3 version 4 is the step which assigns the correct sizes for
+borders, decoration windows, child windows and the stacking order of all
+windows. In a separate step (x_push_changes()), these changes are pushed to
+X11.
+
Keep in mind that all these properties (rect, window_rect and deco_rect)
+are temporary, meaning they will be overwritten by calling render_con.
+Persistent position/size information is kept in geometry.
+
The entry point for every rendering operation (except for the case of moving
+floating windows around) currently is tree_render() which will re-render
+everything thatâs necessary (for every output, only the currently displayed
+workspace is rendered). This behavior is expected to change in the future,
+since for a lot of updates, re-rendering everything is not actually necessary.
+Focus was on getting it working correct, not getting it work very fast.
+
What tree_render() actually does is calling render_con() on the root
+container and then pushing the changes to X11. The following sections talk
+about the different rendering steps, in the order of "top of the tree" (root
+container) to the bottom.
-
14.1. Common parts
-
On the frame (the window which was created around the clientâs window for the
-decorations), a black rectangle is drawn as a background for windows like
-MPlayer, which do not completely fit into the frame.
+
14.1. Rendering the root container
+
The i3 root container (con->type == CT_ROOT) represents the X11 root window.
+It contains one child container for every output (like LVDS1, VGA1, â¦), which
+is available on your computer.
+
Rendering the root will first render all tiling windows and then all floating
+windows. This is necessary because a floating window can be positioned in such
+a way that it is visible on two different outputs. Therefore, by first
+rendering all the tiling windows (of all outputs), we make sure that floating
+windows can never be obscured by tiling windows.
+
Essentially, though, this code path will just call render_con() for every
+output and x_raise_con(); render_con() for every floating window.
+
In the special case of having a "global fullscreen" window (fullscreen mode
+spanning all outputs), a shortcut is taken and x_raise_con(); render_con() is
+only called for the global fullscreen window.
-
14.2. Default mode
-
Each clients gets the containerâs width and an equal amount of height.
+
14.2. Rendering an output
+
Output containers (con->layout == L_OUTPUT) represent a hardware output like
+LVDS1, VGA1, etc. An output container has three children (at the moment): One
+content container (having workspaces as children) and the top/bottom dock area
+containers.
+
The rendering happens in the function render_l_output() in the following
+steps:
+
+-
+
+Find the content container (con->type == CT_CON)
+
+
+-
+
+Get the currently visible workspace (con_get_fullscreen_con(content,
+ CF_OUTPUT)).
+
+
+-
+
+If there is a fullscreened window on that workspace, directly render it and
+ return, thus ignoring the dock areas.
+
+
+-
+
+Sum up the space used by all the dock windows (they have a variable height
+ only).
+
+
+-
+
+Set the workspace rects (x/y/width/height) based on the position of the
+ output (stored in con->rect) and the usable space
+ (con->rect.{width,height} without the space used for dock windows).
+
+
+-
+
+Recursively raise and render the outputâs child containers (meaning dock
+ area containers and the content container).
+
+
+
-
14.3. Stack mode
-
In stack mode, a window containing the decorations of all windows inside the
-container is placed at the top. The currently focused window is then given the
-whole remaining space.
+
14.3. Rendering a workspace or split container
+
From here on, there really is no difference anymore. All containers are of
+con->type == CT_CON (whether workspace or split container) and some of them
+have a con->window, meaning they represent an actual window instead of a
+split container.
+
+
14.3.1. Default layout
+
In default layout, containers are placed horizontally or vertically next to
+each other (depending on the con->orientation). If a child is a leaf node (as
+opposed to a split container) and has border style "normal", appropriate space
+will be reserved for its window decoration.
-
-
14.4. Tabbed mode
-
Tabbed mode is like stack mode, except that the window decorations are drawn
-in one single line at the top of the container.
+
+
14.3.2. Stacked layout
+
In stacked layout, only the focused window is actually shown (this is achieved
+by calling x_raise_con() in reverse focus order at the end of render_con()).
+
The available space for the focused window is the size of the container minus
+the height of the window decoration for all windows inside this stacked
+container.
+
If border style is "1pixel" or "none", no window decoration height will be
+reserved (or displayed later on), unless there is more than one window inside
+the stacked container.
+
+
+
14.3.3. Tabbed layout
+
Tabbed layout works precisely like stacked layout, but the window decoration
+position/size is different: They are placed next to each other on a single line
+(fixed height).
+
+
+
14.3.4. Dock area layout
+
This is a special case. Users cannot choose the dock area layout, but it will be
+set for the dock area containers. In the dockarea layout (at the moment!),
+windows will be placed above each other.
-
-
14.5. Window decorations
-
The window decorations consist of a rectangle in the appropriate color (depends
-on whether this window is the currently focused one, the last focused one in a
-not focused container or not focused at all) forming the background.
-Afterwards, two lighter lines are drawn and the last step is drawing the
-windowâs title (see WM_NAME) onto it.
-
14.6. Fullscreen windows
-
For fullscreen windows, the rect (x, y, width, height) is not changed to
-allow the client to easily go back to its previous position. Instead,
-fullscreen windows are skipped when rendering.
+
14.4. Rendering a window
+
A windowâs size and position will be determined in the following way:
+
+-
+
+Subtract the border if border style is not "none" (but "normal" or "1pixel").
+
+
+-
+
+Subtract the X11 border, if the window has an X11 border > 0.
+
+
+-
+
+Obey the aspect ratio of the window (think MPlayer).
+
+
+-
+
+Obey the height- and width-increments of the window (think terminal emulator
+ which can only be resized in one-line or one-character steps).
+
+
+
+
+
+
+
15. Pushing updates to X11 / Drawing
+
+
A big problem with i3 before version 4 was that we just sent requests to X11
+anywhere in the source code. This was bad because nobody could understand the
+entirety of our interaction with X11, it lead to subtle bugs and a lot of edge
+cases which we had to consider all over again.
+
Therefore, since version 4, we have a single file, src/x.c, which is
+responsible for repeatedly transferring parts of our tree datastructure to X11.
+
src/x.c consists of multiple parts:
+
+-
+
+The state pushing: x_push_changes(), which calls x_push_node().
+
+
+-
+
+State modification functions: x_con_init, x_reinit,
+ x_reparent_child, x_move_win, x_con_kill, x_raise_con, x_set_name
+ and x_set_warp_to.
+
+
+-
+
+Expose event handling (drawing decorations): x_deco_recurse() and
+ x_draw_decoration().
+
+
+
-
14.7. Resizing containers
-
By clicking and dragging the border of a container, you can resize the whole
-column (respectively row) which this container is in. This is necessary to keep
-the table layout working and consistent.
-
The resizing works similarly to the resizing of floating windows or movement of
-floating windows:
-
+15.1. Pushing state to X11
+In general, the function x_push_changes should be called to push state
+changes. Only when the scope of the state change is clearly defined (for
+example only the title of a window) and its impact is known beforehand, one can
+optimize this and call x_push_node on the appropriate con directly.
+x_push_changes works in the following steps:
+
-
-A new, invisible window with the size of the root window is created
- (grabwin)
+Clear the eventmask for all mapped windows. This leads to not getting
+ useless ConfigureNotify or EnterNotify events which are caused by our
+ requests. In general, we only want to handle user input.
-
-Another window, 2px width and as high as your screen (or vice versa for
- horizontal resizing) is created. Its background color is the border color and
- it is only there to inform the user how big the container will be (it
- creates the impression of dragging the border out of the container).
+Stack windows above each other, in reverse stack order (starting with the
+ most obscured/bottom window). This is relevant for floating windows which
+ can overlap each other, but also for tiling windows in stacked or tabbed
+ containers. We also update the _NET_CLIENT_LIST_STACKING hint which is
+ necessary for tab drag and drop in Chromium.
-
-The drag_pointer function of src/floating.c is called to grab the pointer
- and enter its own event loop which will pass all events (expose events) but
- motion notify events. This function then calls the specified callback
- (resize_callback) which does some boundary checking and moves the helper
- window. As soon as the mouse button is released, this loop will be
- terminated.
+x_push_node will be called for the root container, recursively calling
+ itself for the containerâs children. This function actually pushes the
+ state, see the next paragraph.
-
-The new width_factor for each involved column (respectively row) will be
- calculated.
+If the pointer needs to be warped to a different position (for example when
+ changing focus to a differnt output), it will be warped now.
-
+-
+
+The eventmask is restored for all mapped windows.
+
+
+-
+
+Window decorations will be rendered by calling x_deco_recurse on the root
+ container, which then recursively calls itself for the children.
+
+
+-
+
+If the input focus needs to be changed (because the user focused a different
+ window), it will be updated now.
+
+
+-
+
+x_push_node_unmaps will be called for the root container. This function
+ only pushes UnmapWindow requests. Separating the state pushing is necessary
+ to handle fullscreen windows (and workspace switches) in a smooth fashion:
+ The newly visible windows should be visible before the old windows are
+ unmapped.
+
+
+
+
x_push_node works in the following steps:
+
+-
+
+Update the windowâs WM_NAME, if changed (the WM_NAME is set on i3
+ containers mainly for debugging purposes).
+
+
+-
+
+Reparents a child window into the i3 container if the container was created
+ for a specific managed window.
+
+
+-
+
+If the size/position of the i3 container changed (due to opening a new
+ window or switching layouts for example), the window will be reconfigured.
+ Also, the pixmap which is used to draw the window decoration/border on is
+ reconfigured (pixmaps are size-dependent).
+
+
+-
+
+Size/position for the child window is adjusted.
+
+
+-
+
+The i3 container is mapped if it should be visible and was not yet mapped.
+ When mapping, WM_STATE is set to WM_STATE_NORMAL. Also, the eventmask of
+ the child window is updated and the i3 containerâs contents are copied from
+ the pixmap.
+
+
+-
+
+x_push_node is called recursively for all children of the current
+ container.
+
+
+
+
x_push_node_unmaps handles the remaining case of an i3 container being
+unmapped if it should not be visible anymore. WM_STATE will be set to
+WM_STATE_WITHDRAWN.
+
+
+
15.2. Drawing window decorations/borders/backgrounds
+
x_draw_decoration draws window decorations. It is run for every leaf
+container (representing an actual X11 window) and for every non-leaf container
+which is in a stacked/tabbed container (because stacked/tabbed containers
+display a window decoration for split containers, which consists of a representation
+of the child container’s names.
+
Then, parameters are collected to be able to determine whether this decoration
+drawing is actually necessary or was already done. This saves a substantial
+number of redraws (depending on your workload, but far over 50%).
+
Assuming that we need to draw this decoration, we start by filling the empty
+space around the child window (think of MPlayer with a specific aspect ratio)
+in the user-configured client background color.
+
Afterwards, we draw the appropriate border (in case of border styles "normal"
+and "1pixel") and the top bar (in case of border style "normal").
+
The last step is drawing the window title on the top bar.
-
15. User commands / commandmode (src/commands.c)
+
16. User commands (parser-specs/commands.spec)
-
Like in vim, you can control i3 using commands. They are intended to be a
-powerful alternative to lots of shortcuts, because they can be combined. There
-are a few special commands, which are the following:
+
In the configuration file and when using i3 interactively (with i3-msg, for
+example), you use commands to make i3 do things, like focus a different window,
+set a window to fullscreen, and so on. An example command is floating enable,
+which enables floating mode for the currently focused window. See the
+appropriate section in the Userâs Guide for a reference of
+all commands.
+
In earlier versions of i3, interpreting these commands was done using lex and
+yacc, but experience has shown that lex and yacc are not well suited for our
+command language. Therefore, starting from version 4.2, we use a custom parser
+for user commands and the configuration file.
+The input specification for this parser can be found in the file
+parser-specs/*.spec. Should you happen to use Vim as an editor, use
+:source parser-specs/highlighting.vim to get syntax highlighting for this file
+(highlighting files for other editors are welcome).
+
+
Excerpt from commands.spec
+
+
state INITIAL:
+ '[' -> call cmd_criteria_init(); CRITERIA
+ 'move' -> MOVE
+ 'exec' -> EXEC
+ 'workspace' -> WORKSPACE
+ 'exit' -> call cmd_exit()
+ 'restart' -> call cmd_restart()
+ 'reload' -> call cmd_reload()
+
+
The input specification is written in an extremely simple format. The
+specification is then converted into C code by the Perl script
+generate-commands-parser.pl (the output file names begin with GENERATED and the
+files are stored in the include directory). The parser implementation
+src/commands_parser.c includes the generated C code at compile-time.
+
The above excerpt from commands.spec illustrates nearly all features of our
+specification format: You describe different states and what can happen within
+each state. State names are all-caps; the state in the above excerpt is called
+INITIAL. A list of tokens and their actions (separated by an ASCII arrow)
+follows. In the excerpt, all tokens are literals, that is, simple text strings
+which will be compared with the input. An action is either the name of a state
+in which the parser will transition into, or the keyword call, followed by
+the name of a function (and optionally a state).
+
+
16.1. Example: The WORKSPACE state
+
Letâs have a look at the WORKSPACE state, which is a good example of all
+features. This is its definition:
+
+
WORKSPACE state (commands.spec)
+
+
# workspace next|prev|next_on_output|prev_on_output
+# workspace back_and_forth
+# workspace <name>
+# workspace number <number>
+state WORKSPACE:
+ direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
+ -> call cmd_workspace($direction)
+ 'back_and_forth'
+ -> call cmd_workspace_back_and_forth()
+ 'number'
+ -> WORKSPACE_NUMBER
+ workspace = string
+ -> call cmd_workspace_name($workspace)
+
+
As you can see from the commands, there are multiple different valid variants
+of the workspace command:
-
-exec <command>
+workspace <direction>
-
-Starts the given command by passing it to /bin/sh.
+ The word workspace can be followed by any of the tokens next,
+ prev, next_on_output or prev_on_output. This command will
+ switch to the next or previous workspace (optionally on the same
+ output).
+ There is one function called cmd_workspace, which is defined
+ in src/commands.c. It will handle this kind of command. To know which
+ direction was specified, the direction token is stored on the stack
+ with the name "direction", which is what the "direction = " means in
+ the beginning.
+
+
+
+
+ Note
+ |
+Note that you can specify multiple literals in the same line. This has
+ exactly the same effect as if you specified direction =
+ 'next_on_output' -> call cmd_workspace($direction) and so forth.
|
+
+
+
+
+
+ Note
+ |
+Also note that the order of literals is important here: If next were
+ ordered before next_on_output, then next_on_output would never
+ match. |
+
+
+
-
-restart
+workspace back_and_forth
-
-Restarts i3 by executing argv[0] (the path with which you started i3) without
-forking.
+ This is a very simple case: When the literal back_and_forth is found
+ in the input, the function cmd_workspace_back_and_forth will be
+ called without parameters and the parser will return to the INITIAL
+ state (since no other state was specified).
-
-w
+workspace <name>
-
-"With". This is used to select a bunch of windows. Currently, only selecting
-the whole container in which the window is in, is supported by specifying "w".
+ In this case, the workspace command is followed by an arbitrary string,
+ possibly in quotes, for example "workspace 3" or "workspace bleh".
+ This is the first time that the token is actually not a literal (not in
+ single quotes), but just called string. Other possible tokens are word
+ (the same as string, but stops matching at a whitespace) and end
+ (matches the end of the input).
-
-f, s, d
+workspace number <number>
-
-Toggle fullscreen, stacking, default mode for the current window/container.
+ The workspace command has to be followed by the keyword number. It
+ then transitions into the state WORKSPACE_NUMBER, where the actual
+ parameter will be read.
-
The other commands are to be combined with a direction. The directions are h,
-j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just
-specify the direction keys, i3 will move the focus in that direction. You can
-provide "m" or "s" before the direction to move a window respectively or snap.
+
+
+
16.2. Introducing a new command
+
The following steps have to be taken in order to properly introduce a new
+command (or possibly extend an existing command):
+
+-
+
+Define a function beginning with cmd_ in the file src/commands.c. Copy
+ the prototype of an existing function.
+
+
+-
+
+After adding a comment on what the function does, copy the comment and
+ function definition to include/commands.h. Make the comment in the header
+ file use double asterisks to make doxygen pick it up.
+
+
+-
+
+Write a test case (or extend an existing test case) for your feature, see
+ i3 testsuite. For now, it is sufficient to simply call
+ your command in all the various possible ways.
+
+
+-
+
+Extend the parser specification in parser-specs/commands.spec. Run the
+ testsuite and see if your new function gets called with the appropriate
+ arguments for the appropriate input.
+
+
+-
+
+Actually implement the feature.
+
+
+-
+
+Document the feature in the Userâs Guide.
+
+
+
+
-
16. Gotchas
+
17. Moving containers
+
+
The movement code is pretty delicate. You need to consider all cases before
+making any changes or before being able to fully understand how it works.
+
+
17.1. Case 1: Moving inside the same container
+
The reference layout for this case is a single workspace in horizontal
+orientation with two containers on it. Focus is on the left container (1).
+
+
When moving the left window to the right (command move right), tree_move will
+look for a container with horizontal orientation and finds the parent of the
+left container, that is, the workspace. Afterwards, it runs the code branch
+commented with "the easy case": it calls TAILQ_NEXT to get the container right
+of the current one and swaps both containers.
+
+
+
17.2. Case 2: Move a container into a split container
+
The reference layout for this case is a horizontal workspace with two
+containers. The right container is a v-split with two containers. Focus is on
+the left container (1).
+
+
+
+
+
+
+1 |
+2 |
+
+
+3 |
+
+
+
+
+
When moving to the right (command move right), i3 will work like in case 1
+("the easy case"). However, as the right container is not a leaf container, but
+a v-split, the left container (1) will be inserted at the right position (below
+2, assuming that 2 is focused inside the v-split) by calling insert_con_into.
+
insert_con_into detaches the container from its parent and inserts it
+before/after the given target container. Afterwards, the on_remove_child
+callback is called on the old parent container which will then be closed, if
+empty.
+
Afterwards, con_focus will be called to fix the focus stack and the tree will
+be flattened.
+
+
+
17.3. Case 3: Moving to non-existant top/bottom
+
Like in case 1, the reference layout for this case is a single workspace in
+horizontal orientation with two containers on it. Focus is on the left
+container:
+
+
This time however, the command is move up or move down. tree_move will look
+for a container with vertical orientation. As it will not find any,
+same_orientation is NULL and therefore i3 will perform a forced orientation
+change on the workspace by creating a new h-split container, moving the
+workspace contents into it and then changing the workspace orientation to
+vertical. Now it will again search for parent containers with vertical
+orientation and it will find the workspace.
+
This time, the easy case code path will not be run as we are not moving inside
+the same container. Instead, insert_con_into will be called with the focused
+container and the container above/below the current one (on the level of
+same_orientation).
+
Now, con_focus will be called to fix the focus stack and the tree will be
+flattened.
+
+
+
17.4. Case 4: Moving to existant top/bottom
+
The reference layout for this case is a vertical workspace with two containers.
+The bottom one is a h-split containing two containers (1 and 2). Focus is on
+the bottom left container (1).
+
+
+
+
+
+
+3 |
+
+
+1 |
+2 |
+
+
+
+
+
This case is very much like case 3, only this time the forced workspace
+orientation change does not need to be performed because the workspace already
+is in vertical orientation.
+
+
+
17.5. Case 5: Moving in one-child h-split
+
The reference layout for this case is a horizontal workspace with two
+containers having a v-split on the left side with a one-child h-split on the
+bottom. Focus is on the bottom left container (2(h)):
+
+
+
+
+
+
+1 |
+3 |
+
+
+2(h) |
+
+
+
+
+
In this case, same_orientation will be set to the h-split container around
+the focused container. However, when trying the easy case, the next/previous
+container swap will be NULL. Therefore, i3 will search again for a
+same_orientation container, this time starting from the parent of the h-split
+container.
+
After determining a new same_orientation container (if it is NULL, the
+orientation will be force-changed), this case is equivalent to case 2 or case
+4.
+
+
+
17.6. Case 6: Floating containers
+
The reference layout for this case is a horizontal workspace with two
+containers plus one floating h-split container. Focus is on the floating
+container.
+
TODO: nice illustration. table not possible?
+
When moving up/down, the container needs to leave the floating container and it
+needs to be placed on the workspace (at workspace level). This is accomplished
+by calling the function attach_to_workspace.
+
+
+
+
+
18. Click handling
+
+
Without much ado, here is the list of cases which need to be considered:
+
+-
+
+click to focus (tiling + floating) and raise (floating)
+
+
+-
+
+click to focus/raise when in stacked/tabbed mode
+
+
+-
+
+floating_modifier + left mouse button to drag a floating con
+
+
+-
+
+floating_modifier + right mouse button to resize a floating con
+
+
+-
+
+click on decoration in a floating con to either initiate a resize (if there
+ is more than one child in the floating con) or to drag the
+ floating con (if itâs the one at the top).
+
+
+-
+
+click on border in a floating con to resize the floating con
+
+
+-
+
+floating_modifier + right mouse button to resize a tiling con
+
+
+-
+
+click on border/decoration to resize a tiling con
+
+
+
+
+
+
+
19. Gotchas
-
@@ -849,36 +1484,130 @@ Forgetting to call xcb_flush(conn); after sending a request. This usual
certain conditions.
+-
+
+Forgetting to call floating_fix_coordinates(con, old_rect, new_rect) after
+ moving workspaces across outputs. Coordinates for floating containers are
+ not relative to workspace boundaries, so you must correct their coordinates
+ or those containers will show up in the wrong workspace or not at all.
+
+
-
17. Using git / sending patches
+
20. Using git / sending patches
+
+
20.1. Introduction
-
When you want to send a patch because you fixed a bug or implemented a cool
-feature (please talk to us before working on features to see whether they are
-maybe already implemented, not possible for some some reason, or donât fit
-into the concept), please use git to create a patchfile.
-
First of all, update your working copy to the latest version of the master
-branch:
-
-
Afterwards, make the necessary changes for your bugfix/feature. Then, review
-the changes using git diff (you might want to enable colors in the diff using
-git config diff.color auto). When you are definitely done, use git commit
--a to commit all changes youâve made.
-
Then, use the following command to generate a patchfile which we can directly
-apply to the branch, preserving your commit message and name:
-
-
-
git format-patch origin
-
-
Just send us the generated file via email.
+
http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy
+or, for more documentation, see
http://git-scm.com/documentation
+
Please talk to us before working on new features to see whether they will be
+accepted. A good way for this is to open an issue and asking for opinions on it.
+Even for accepted features, this can be a good way to refine an idea upfront. However,
+we don’t want to see certain features in i3, e.g., switching window focus in an
+Alt+Tab like way.
+
When working on bugfixes, please make sure you mention that you are working on
+it in the corresponding bug report at https://github.com/i3/i3/issues. In case
+there is no bug report yet, please create one.
+
+
Do not send emails to the mailing list or any author directly, and donât submit
+them in the bugtracker, since all reviews should be done in public at
+https://github.com/i3/i3. In order to make your review go as fast as possible, you
+could have a look at previous reviews and see what the common mistakes are.
+
+
+
20.2. Which branch to use?
+
Work on i3 generally happens in two branches: âmasterâ and ânextâ (the latter
+being the default branch, the one that people get when they check out the git
+repository).
+
The contents of âmasterâ are always stable. That is, it contains the source code
+of the latest release, plus any bugfixes that were applied since that release.
+
New features are only found in the ânextâ branch. Therefore, if you are working
+on a new feature, use the ânextâ branch. If you are working on a bugfix, use the
+ânextâ branch, too, but make sure your code also works on âmasterâ.
+
+
+