2009-03-07 00:50:18 +01:00
|
|
|
|
Hacking i3: How To
|
|
|
|
|
==================
|
|
|
|
|
Michael Stapelberg <michael+i3@stapelberg.de>
|
|
|
|
|
March 2009
|
|
|
|
|
|
|
|
|
|
This document is intended to be the first thing you read before looking and/or touching
|
|
|
|
|
i3’s source code. It should contain all important information to help you understand
|
|
|
|
|
why things are like they are. If it does not mention something you find necessary, please
|
|
|
|
|
do not hesitate to contact me.
|
|
|
|
|
|
|
|
|
|
== Window Managers
|
|
|
|
|
|
|
|
|
|
A window manager is not necessarily needed to run X, but it is usually used in combination
|
|
|
|
|
to facilitate some things. The window manager's job is to take care of the placement of
|
|
|
|
|
windows, to provide the user some mechanisms to change the position/size of windows and
|
|
|
|
|
to communicate with clients to a certain extent (for example handle fullscreen requests
|
|
|
|
|
of clients such as MPlayer).
|
|
|
|
|
|
|
|
|
|
There are no different contexts in which X11 clients run, so a window manager is just another
|
|
|
|
|
client, like all other X11 applications. However, it handles some events which normal clients
|
|
|
|
|
usually don’t handle.
|
|
|
|
|
|
|
|
|
|
In the case of i3, the tasks (and order of them) are the following:
|
|
|
|
|
|
|
|
|
|
. Grab the key bindings (events will be sent upon keypress/keyrelease)
|
|
|
|
|
. Iterate through all existing windows (if the window manager is not started as the first
|
|
|
|
|
client of X) and manage them (= reparent them, create window decorations)
|
|
|
|
|
. When new windows are created, manage them
|
2009-03-07 06:24:31 +01:00
|
|
|
|
. Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
|
|
|
|
|
. Handle the client’s `WM_NAME` property
|
2009-03-07 00:50:18 +01:00
|
|
|
|
. Handle the client’s size hints to display them proportionally
|
|
|
|
|
. Handle enter notifications (focus follows mouse)
|
|
|
|
|
. Handle button (as in mouse buttons) presses for focus/raise on click
|
|
|
|
|
. Handle expose events to re-draw own windows such as decorations
|
|
|
|
|
. React to the user’s commands: Change focus, Move windows, Switch workspaces,
|
|
|
|
|
Change the layout mode of a container (default/stacking), Start a new application,
|
|
|
|
|
Restart the window manager
|
|
|
|
|
|
|
|
|
|
In the following chapters, each of these tasks and their implementation details will be discussed.
|
|
|
|
|
|
|
|
|
|
== Files
|
|
|
|
|
|
|
|
|
|
include/data.h::
|
|
|
|
|
Contains data definitions used by nearly all files. You really need to read this first.
|
|
|
|
|
|
|
|
|
|
include/*.h::
|
|
|
|
|
Contains forward definitions for all public functions.
|
|
|
|
|
|
|
|
|
|
src/commands.c::
|
|
|
|
|
Parsing commands
|
|
|
|
|
|
|
|
|
|
src/config.c::
|
|
|
|
|
Parses the configuration file
|
|
|
|
|
|
|
|
|
|
src/debug.c::
|
|
|
|
|
Contains debugging functions to print unhandled X events
|
|
|
|
|
|
|
|
|
|
src/handlers.c::
|
|
|
|
|
Contains all handlers for all kind of X events
|
|
|
|
|
|
|
|
|
|
src/layout.c::
|
|
|
|
|
Renders your layout (screens, workspaces, containers)
|
|
|
|
|
|
|
|
|
|
src/mainx.c::
|
|
|
|
|
Initializes the window manager
|
|
|
|
|
|
|
|
|
|
src/table.c::
|
|
|
|
|
Manages the most important internal data structure, the design table.
|
|
|
|
|
|
|
|
|
|
src/util.c::
|
|
|
|
|
Contains useful functions which are not really dependant on anything.
|
|
|
|
|
|
|
|
|
|
src/xcb.c::
|
|
|
|
|
Contains wrappers to use xcb more easily.
|
|
|
|
|
|
|
|
|
|
src/xinerama.c::
|
|
|
|
|
(Re-)initializes the available screens and converts them to virtual screens (see below).
|
|
|
|
|
|
|
|
|
|
== Data structures
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
See include/data.h for documented data structures. The most important ones are explained
|
|
|
|
|
right here.
|
|
|
|
|
|
|
|
|
|
TODO: We need a slick graphic here
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
=== Virtual screens
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
A virtual screen (type `i3Screen`) is generated from the connected screens obtained
|
2009-03-07 00:50:18 +01:00
|
|
|
|
through Xinerama. The difference to the raw Xinerama monitors as seen when using xrandr(1)
|
|
|
|
|
is that it falls back to the lowest common resolution of the logical screens.
|
|
|
|
|
|
|
|
|
|
For example, if your notebook has 1280x800 and you connect a video projector with
|
2009-03-07 06:24:31 +01:00
|
|
|
|
1024x768, set up in clone mode (xrandr \--output VGA \--mode 1024x768 \--same-as LVDS),
|
2009-03-07 00:50:18 +01:00
|
|
|
|
i3 will have one virtual screen.
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
However, if you configure it using xrandr \--output VGA \--mode 1024x768 \--right-of LVDS,
|
2009-03-07 00:50:18 +01:00
|
|
|
|
i3 will generate two virtual screens. For each virtual screen, a new workspace will be
|
|
|
|
|
assigned. New workspaces are created on the screen you are currently on.
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
=== Workspace
|
|
|
|
|
|
|
|
|
|
A workspace is identified by its number. Basically, you could think of workspaces
|
|
|
|
|
as different desks in your bureau, if you like the desktop methaphor. They just contain
|
|
|
|
|
different sets of windows and are completely separate of each other. Other window
|
|
|
|
|
managers also call this ``Virtual desktops''.
|
|
|
|
|
|
|
|
|
|
=== The layout table
|
|
|
|
|
|
|
|
|
|
Each workspace has a table, which is just a two-dimensional dynamic array containing
|
|
|
|
|
Containers (see below). This table grows and shrinks as you need it (by moving windows
|
|
|
|
|
to the right you can create a new column in the table, by moving them to the bottom
|
|
|
|
|
you create a new row).
|
|
|
|
|
|
|
|
|
|
=== Container
|
|
|
|
|
|
|
|
|
|
A container is the content of a table’s cell. It holds an arbitrary amount of windows
|
|
|
|
|
and has a specific layout (default layout or stack layout). Containers can consume
|
|
|
|
|
multiple table cells by modifying their colspan/rowspan attribute.
|
|
|
|
|
|
|
|
|
|
=== Client
|
|
|
|
|
|
|
|
|
|
A client is x11-speak for a window.
|
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
== List/queue macros
|
|
|
|
|
|
|
|
|
|
i3 makes heavy use of the list macros defined in BSD operating systems. To ensure
|
|
|
|
|
that the operating system on which i3 is compiled has all the awaited features,
|
2009-03-07 06:24:31 +01:00
|
|
|
|
i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux,
|
2009-03-07 00:50:18 +01:00
|
|
|
|
you have to use google.
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
The lists used are `SLIST` (single linked lists) and `CIRCLEQ` (circular queues).
|
|
|
|
|
Usually, only forward traversal is necessary, so an `SLIST` works fine. However,
|
|
|
|
|
for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently
|
2009-03-07 00:50:18 +01:00
|
|
|
|
selected window to the window above/below.
|
|
|
|
|
|
|
|
|
|
== Naming conventions
|
|
|
|
|
|
|
|
|
|
There is a row of standard variables used in many events. The following names should be
|
|
|
|
|
chosen for those:
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
* ``conn'' is the xcb_connection_t
|
|
|
|
|
* ``event'' is the event of the particular type
|
|
|
|
|
* ``container'' names a container
|
|
|
|
|
* ``client'' names a client, for example when using a `CIRCLEQ_FOREACH`
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
== Startup (src/mainx.c, main())
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
* Establish the xcb connection
|
|
|
|
|
* Check for XKB extension on the separate X connection
|
|
|
|
|
* Check for Xinerama screens
|
|
|
|
|
* Grab the keycodes for which bindings exist
|
|
|
|
|
* Manage all existing windows
|
|
|
|
|
* Enter the event loop
|
|
|
|
|
|
|
|
|
|
== Keybindings
|
|
|
|
|
|
|
|
|
|
=== Grabbing the bindings
|
|
|
|
|
|
|
|
|
|
Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and
|
|
|
|
|
the keycode you want to grab and whether you want to grab them actively or passively. Most
|
|
|
|
|
bindings (everything except for bindings using Mode_switch) are grabbed passively, that is,
|
|
|
|
|
just the window manager gets the event and cannot replay it.
|
|
|
|
|
|
|
|
|
|
We need to grab bindings that use Mode_switch actively because of a bug in X. When the window
|
|
|
|
|
manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide
|
|
|
|
|
what to do with this event: It can either replay it so that other applications get it or it
|
|
|
|
|
can prevent other applications from receiving it.
|
|
|
|
|
|
|
|
|
|
So, why do we need to grab keycodes actively? Because X does not set the state-property of
|
|
|
|
|
keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it
|
|
|
|
|
using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch
|
|
|
|
|
when grabbing the key and therefore need to grab the keycode itself without any modiffiers.
|
|
|
|
|
This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
|
|
|
|
|
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle
|
|
|
|
|
the event, if not, it will replay the event.
|
|
|
|
|
|
|
|
|
|
=== Handling a keypress
|
|
|
|
|
|
|
|
|
|
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets the correct state.
|
|
|
|
|
|
|
|
|
|
Then, it looks through all bindings and gets the one which matches the received event.
|
|
|
|
|
|
|
|
|
|
The bound command is parsed directly in command mode.
|
|
|
|
|
|
|
|
|
|
== Manage windows (src/mainx.c, manage_window() and reparent_window())
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
`manage_window()` does some checks to decide whether the window should be managed at all:
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
* Windows have to be mapped, that is, visible on screen
|
|
|
|
|
* The override_redirect must not be set. Windows with override_redirect shall not be
|
|
|
|
|
managed by a window manager
|
|
|
|
|
|
|
|
|
|
Afterwards, i3 gets the intial geometry and reparents the window if it wasn’t already
|
|
|
|
|
managed.
|
|
|
|
|
|
|
|
|
|
Reparenting means that for each window which is reparented, a new window, slightly larger
|
|
|
|
|
than the original one, is created. The original window is then reparented to the bigger one
|
|
|
|
|
(called "frame").
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this
|
|
|
|
|
window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled
|
2009-03-07 00:50:18 +01:00
|
|
|
|
differently, they don’t have decorations and are not assigned to a specific container.
|
|
|
|
|
Instead, they are positioned at the bottom of the screen. To get the height which needsd
|
2009-03-07 06:24:31 +01:00
|
|
|
|
to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== What happens when an application is started?
|
|
|
|
|
|
|
|
|
|
i3 does not care for applications. All it notices is when new windows are mapped (see
|
2009-03-07 06:24:31 +01:00
|
|
|
|
`src/handlers.c`, `handle_map_notify_event()`). The window is then reparented (see section
|
2009-03-07 00:50:18 +01:00
|
|
|
|
"Manage windows").
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
After reparenting the window, `render_layout()` is called which renders the internal
|
2009-03-07 00:50:18 +01:00
|
|
|
|
layout table. The window was placed in the currently focused container and
|
|
|
|
|
therefore the new window and the old windows (if any) need te be moved/resized
|
|
|
|
|
so that the currently active layout (default mode/stacking mode) is rendered
|
2009-03-07 06:24:31 +01:00
|
|
|
|
correctly. To move/resize windows, a window is ``configured'' in X11-speak.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
Some applications, such as MPlayer obivously assume the window manager is stupid
|
|
|
|
|
and therefore configure their windows by themselves. This generates an event called
|
|
|
|
|
configurenotify. i3 handles these events and pushes the window back to its position/size.
|
|
|
|
|
|
|
|
|
|
== _NET_WM_STATE
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the
|
2009-03-07 00:50:18 +01:00
|
|
|
|
specific client which just configures the client to use the whole screen on which it
|
|
|
|
|
currently is. Also, it is set as fullscreen_client for the i3Screen.
|
|
|
|
|
|
|
|
|
|
== WM_NAME
|
|
|
|
|
|
|
|
|
|
When the WM_NAME property of a window changes, its decoration (containing the title)
|
|
|
|
|
is re-rendered.
|
|
|
|
|
|
|
|
|
|
== Size hints
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
TODO
|
|
|
|
|
|
|
|
|
|
== Rendering (src/layout.c, render_layout() and render_container())
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
There are two entry points to rendering: render_layout() and render_container(). The
|
|
|
|
|
former one renders all virtual screens, the currently active workspace of each virtual
|
|
|
|
|
screen and all containers (inside the table cells) of these workspaces using
|
|
|
|
|
render_container(). 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_layout(), each container
|
|
|
|
|
gets its position (screen offset + offset in the table) and size (container's width
|
|
|
|
|
times colspan/rowspan). Then, render_container() is called:
|
|
|
|
|
|
|
|
|
|
render_container() then takes different approaches, depending on the mode the container
|
|
|
|
|
is in.
|
|
|
|
|
|
|
|
|
|
=== 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 don’t completely
|
|
|
|
|
fit into the frame.
|
|
|
|
|
|
|
|
|
|
=== Default mode
|
|
|
|
|
|
|
|
|
|
Each clients gets the container’s width and an equal amount of height.
|
|
|
|
|
|
|
|
|
|
=== 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.
|
|
|
|
|
|
|
|
|
|
=== Window decorations
|
|
|
|
|
|
|
|
|
|
The window decorations consist of a rectangle in the appropriate color (depends on whether
|
|
|
|
|
this window is the currently focused one or 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.
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
=== 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.
|
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
=== Resizing containers
|
|
|
|
|
|
|
|
|
|
By clicking and dragging the border of a container, you can resize it freely.
|
|
|
|
|
|
|
|
|
|
TODO
|
|
|
|
|
|
|
|
|
|
== User commands / commandmode (src/commands.c)
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
exec <command>::
|
|
|
|
|
Starts the given command by passing it to `/bin/sh`.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
restart::
|
2009-03-07 06:24:31 +01:00
|
|
|
|
Restarts i3 by executing `argv[0]` (the path with which you started i3) without forking.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
w::
|
|
|
|
|
"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".
|
|
|
|
|
|
|
|
|
|
f, s, d::
|
|
|
|
|
Toggle fullscreen, stacking, default mode for the current window/container.
|
|
|
|
|
|
|
|
|
|
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.
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
|
|
|
|
== Gotchas
|
|
|
|
|
|
|
|
|
|
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to
|
|
|
|
|
code which looks like it works fine but which does not work under certain conditions.
|
|
|
|
|
|