2009-03-07 00:50:18 +01:00
|
|
|
|
Hacking i3: How To
|
|
|
|
|
==================
|
2013-02-10 15:10:13 +01:00
|
|
|
|
Michael Stapelberg <michael@i3wm.org>
|
|
|
|
|
February 2013
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== Window Managers
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
A window manager is not necessarily needed to run X, but it is usually used in
|
|
|
|
|
combination with X to facilitate some things. The window manager's job is to
|
|
|
|
|
take care of the placement of windows, to provide the user with 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).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
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)
|
2010-03-21 01:50:10 +01:00
|
|
|
|
. 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, etc.)
|
2009-03-07 00:50:18 +01:00
|
|
|
|
. When new windows are created, manage them
|
2013-02-26 01:37:35 +01:00
|
|
|
|
. Handle the client’s `_WM_STATE` property, but only `_WM_STATE_FULLSCREEN` and
|
|
|
|
|
`_NET_WM_STATE_DEMANDS_ATTENTION`
|
2009-03-07 06:24:31 +01:00
|
|
|
|
. 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
|
2009-12-07 16:58:46 +01:00
|
|
|
|
. Handle the client’s urgency hint
|
2009-03-07 00:50:18 +01:00
|
|
|
|
. 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,
|
2010-03-21 01:50:10 +01:00
|
|
|
|
Change the layout mode of a container (default/stacking/tabbed), start a new
|
|
|
|
|
application, restart the window manager
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
In the following chapters, each of these tasks and their implementation details
|
|
|
|
|
will be discussed.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-03-07 17:02:17 +01:00
|
|
|
|
=== Tiling window managers
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
Traditionally, there are two approaches to managing windows: The most common
|
|
|
|
|
one nowadays is floating, which means the user can freely move/resize the
|
|
|
|
|
windows. The other approach is called tiling, which means that your window
|
2010-03-21 01:50:10 +01:00
|
|
|
|
manager distributes windows to use as much space as possible while not
|
|
|
|
|
overlapping each other.
|
2009-12-07 16:58:46 +01:00
|
|
|
|
|
|
|
|
|
The idea behind tiling is that you should not need to waste your time
|
|
|
|
|
moving/resizing windows while you usually want to get some work done. After
|
|
|
|
|
all, most users sooner or later tend to lay out their windows in a way which
|
|
|
|
|
corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
|
|
|
|
|
for you? Certainly, it’s faster than you could ever do it.
|
|
|
|
|
|
|
|
|
|
The problem with most tiling window managers is that they are too unflexible.
|
|
|
|
|
In my opinion, a window manager is just another tool, and similar to vim which
|
|
|
|
|
can edit all kinds of text files (like source code, HTML, …) and is not limited
|
|
|
|
|
to a specific file type, a window manager should not limit itself to a certain
|
|
|
|
|
layout (like dwm, awesome, …) but provide mechanisms for you to easily create
|
|
|
|
|
the layout you need at the moment.
|
2009-03-07 17:02:17 +01:00
|
|
|
|
|
2011-11-30 21:55:48 +01:00
|
|
|
|
=== The layout tree
|
2009-03-07 17:02:17 +01:00
|
|
|
|
|
2011-11-30 21:55:48 +01:00
|
|
|
|
The data structure which i3 uses to keep track of your windows is a tree. Every
|
|
|
|
|
node in the tree is a container (type +Con+). Some containers represent actual
|
|
|
|
|
windows (every container with a +window != NULL+), some represent split
|
|
|
|
|
containers and a few have special purposes: they represent workspaces, outputs
|
|
|
|
|
(like VGA1, LVDS1, …) or the X11 root window.
|
2009-03-07 17:02:17 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
So, when you open a terminal and immediately open another one, they reside in
|
2011-11-30 21:55:48 +01:00
|
|
|
|
the same split container, which uses the default layout. In case of an empty
|
|
|
|
|
workspace, the split container we are talking about is the workspace.
|
|
|
|
|
|
|
|
|
|
To get an impression of how different layouts are represented, just play around
|
|
|
|
|
and look at the data structures -- they are exposed as a JSON hash. See
|
2012-09-16 22:55:37 +02:00
|
|
|
|
http://i3wm.org/docs/ipc.html#_tree_reply for documentation on that and an
|
2011-11-30 21:55:48 +01:00
|
|
|
|
example.
|
2009-03-07 17:02:17 +01:00
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
== Files
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
include/atoms.xmacro::
|
|
|
|
|
A file containing all X11 atoms which i3 uses. This file will be included
|
|
|
|
|
various times (for defining, requesting and receiving the atoms), each time
|
|
|
|
|
with a different definition of xmacro().
|
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
include/data.h::
|
2009-12-07 16:58:46 +01:00
|
|
|
|
Contains data definitions used by nearly all files. You really need to read
|
|
|
|
|
this first.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
include/*.h::
|
2010-03-21 01:50:10 +01:00
|
|
|
|
Contains forward definitions for all public functions, as well as
|
2009-12-07 16:58:46 +01:00
|
|
|
|
doxygen-compatible comments (so if you want to get a bit more of the big
|
|
|
|
|
picture, either browse all header files or use doxygen if you prefer that).
|
|
|
|
|
|
|
|
|
|
src/cfgparse.l::
|
|
|
|
|
Contains the lexer for i3’s configuration file, written for +flex(1)+.
|
|
|
|
|
|
|
|
|
|
src/cfgparse.y::
|
|
|
|
|
Contains the parser for i3’s configuration file, written for +bison(1)+.
|
|
|
|
|
|
|
|
|
|
src/click.c::
|
|
|
|
|
Contains all functions which handle mouse button clicks (right mouse button
|
|
|
|
|
clicks initiate resizing and thus are relatively complex).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/cmdparse.l::
|
|
|
|
|
Contains the lexer for i3 commands, written for +flex(1)+.
|
|
|
|
|
|
|
|
|
|
src/cmdparse.y::
|
|
|
|
|
Contains the parser for i3 commands, written for +bison(1)+.
|
2009-05-26 17:25:45 +02:00
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/con.c::
|
|
|
|
|
Contains all functions which deal with containers directly (creating
|
|
|
|
|
containers, searching containers, getting specific properties from containers,
|
|
|
|
|
…).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
src/config.c::
|
2011-07-27 16:32:24 +02:00
|
|
|
|
Contains all functions handling the configuration file (calling the parser
|
|
|
|
|
(src/cfgparse.y) with the correct path, switching key bindings mode).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
src/debug.c::
|
2009-05-26 17:25:45 +02:00
|
|
|
|
Contains debugging functions to print unhandled X events.
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/ewmh.c::
|
2011-11-23 23:17:50 +01:00
|
|
|
|
Functions to get/set certain EWMH properties easily.
|
2011-07-27 16:32:24 +02:00
|
|
|
|
|
2009-05-26 17:25:45 +02:00
|
|
|
|
src/floating.c::
|
|
|
|
|
Contains functions for floating mode (mostly resizing/dragging).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
src/handlers.c::
|
2010-03-21 01:50:10 +01:00
|
|
|
|
Contains all handlers for all kinds of X events (new window title, new hints,
|
2009-05-26 17:25:45 +02:00
|
|
|
|
unmapping, key presses, button presses, …).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-08-19 14:07:52 +02:00
|
|
|
|
src/ipc.c::
|
|
|
|
|
Contains code for the IPC interface.
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/load_layout.c::
|
|
|
|
|
Contains code for loading layouts from JSON files.
|
|
|
|
|
|
|
|
|
|
src/log.c::
|
2012-07-22 00:16:52 +02:00
|
|
|
|
Contains the logging functions.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/main.c::
|
2009-05-26 17:25:45 +02:00
|
|
|
|
Initializes the window manager.
|
|
|
|
|
|
|
|
|
|
src/manage.c::
|
|
|
|
|
Looks at existing or new windows and decides whether to manage them. If so, it
|
|
|
|
|
reparents the window and inserts it into our data structures.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/match.c::
|
|
|
|
|
A "match" is a data structure which acts like a mask or expression to match
|
|
|
|
|
certain windows or not. For example, when using commands, you can specify a
|
|
|
|
|
command like this: [title="*Firefox*"] kill. The title member of the match
|
|
|
|
|
data structure will then be filled and i3 will check each window using
|
|
|
|
|
match_matches_window() to find the windows affected by this command.
|
|
|
|
|
|
|
|
|
|
src/move.c::
|
|
|
|
|
Contains code to move a container in a specific direction.
|
|
|
|
|
|
|
|
|
|
src/output.c::
|
|
|
|
|
Functions to handle CT_OUTPUT cons.
|
|
|
|
|
|
|
|
|
|
src/randr.c::
|
|
|
|
|
The RandR API is used to get (and re-query) the configured outputs (monitors,
|
|
|
|
|
…).
|
|
|
|
|
|
|
|
|
|
src/render.c::
|
|
|
|
|
Renders the tree data structure by assigning coordinates to every node. These
|
|
|
|
|
values will later be pushed to X11 in +src/x.c+.
|
|
|
|
|
|
2009-05-09 17:48:35 +02:00
|
|
|
|
src/resize.c::
|
2011-07-27 16:32:24 +02:00
|
|
|
|
Contains the functions to resize containers.
|
|
|
|
|
|
|
|
|
|
src/sighandler.c::
|
|
|
|
|
Handles +SIGSEGV+, +SIGABRT+ and +SIGFPE+ by showing a dialog that i3 crashed.
|
|
|
|
|
You can chose to let it dump core, to restart it in-place or to restart it
|
|
|
|
|
in-place but forget about the layout.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/tree.c::
|
|
|
|
|
Contains functions which open or close containers in the tree, change focus or
|
|
|
|
|
cleanup ("flatten") the tree. See also +src/move.c+ for another similar
|
|
|
|
|
function, which was moved into its own file because it is so long.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
src/util.c::
|
|
|
|
|
Contains useful functions which are not really dependant on anything.
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/window.c::
|
|
|
|
|
Handlers to update X11 window properties like +WM_CLASS+, +_NET_WM_NAME+,
|
|
|
|
|
+CLIENT_LEADER+, etc.
|
|
|
|
|
|
2009-08-19 14:07:52 +02:00
|
|
|
|
src/workspace.c::
|
|
|
|
|
Contains all functions related to workspaces (displaying, hiding, renaming…)
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/x.c::
|
|
|
|
|
Transfers our in-memory tree (see +src/render.c+) to X11.
|
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
src/xcb.c::
|
|
|
|
|
Contains wrappers to use xcb more easily.
|
|
|
|
|
|
2011-07-27 16:32:24 +02:00
|
|
|
|
src/xcursor.c::
|
|
|
|
|
XCursor functions (for cursor themes).
|
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
src/xinerama.c::
|
2011-07-27 16:32:24 +02:00
|
|
|
|
Legacy support for Xinerama. See +src/randr.c+ for the preferred API.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== Data structures
|
|
|
|
|
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
See include/data.h for documented data structures. The most important ones are
|
|
|
|
|
explained right here.
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// TODO: update image
|
|
|
|
|
|
2009-04-30 17:27:58 +02:00
|
|
|
|
image:bigpicture.png[The Big Picture]
|
|
|
|
|
|
2011-07-31 16:19:25 +02:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2009-04-30 17:27:58 +02:00
|
|
|
|
So, the hierarchy is:
|
|
|
|
|
|
2011-07-27 17:05:28 +02:00
|
|
|
|
. *X11 root window*, the root container
|
2011-08-17 02:26:35 +02:00
|
|
|
|
. *Output container* (LVDS1 in this example)
|
2011-07-27 17:05:28 +02:00
|
|
|
|
. *Content container* (there are also containers for dock windows)
|
|
|
|
|
. *Workspaces* (Workspace 1 in this example, with horizontal orientation)
|
|
|
|
|
. *Split container* (vertically split)
|
|
|
|
|
. *X11 window containers*
|
|
|
|
|
|
|
|
|
|
The data type is +Con+, in all cases.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
=== X11 root window
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
The X11 root window is a single window per X11 display (a display is identified
|
|
|
|
|
by +:0+ or +:1+ etc.). The root window is what you draw your background image
|
|
|
|
|
on. It spans all the available outputs, e.g. +VGA1+ is a specific part of the
|
|
|
|
|
root window and +LVDS1+ is a specific part of the root window.
|
|
|
|
|
|
|
|
|
|
=== Output container
|
|
|
|
|
|
|
|
|
|
Every active output obtained through RandR is represented by one output
|
|
|
|
|
container. Outputs are considered active when a mode is configured (meaning
|
|
|
|
|
something is actually displayed on the output) and the output is not a clone.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:37:19 +02:00
|
|
|
|
For example, if your notebook has a screen resolution of 1280x800 px and you
|
|
|
|
|
connect a video projector with a resolution of 1024x768 px, set it up in clone
|
2011-08-17 02:26:35 +02:00
|
|
|
|
mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), i3 will
|
|
|
|
|
reduce the resolution to the lowest common resolution and disable one of the
|
|
|
|
|
cloned outputs afterwards.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 16:37:19 +02:00
|
|
|
|
However, if you configure it using +xrandr \--output VGA1 \--mode 1024x768
|
2011-08-17 02:26:35 +02:00
|
|
|
|
\--right-of LVDS1+, i3 will set both outputs active. For each output, a new
|
|
|
|
|
workspace will be assigned. New workspaces are created on the output you are
|
|
|
|
|
currently on.
|
|
|
|
|
|
|
|
|
|
=== Content container
|
|
|
|
|
|
|
|
|
|
Each output has multiple children. Two of them are dock containers which hold
|
|
|
|
|
dock clients. The other one is the content container, which holds the actual
|
|
|
|
|
content (workspaces) of this output.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
=== Workspace
|
|
|
|
|
|
2011-07-27 17:05:28 +02:00
|
|
|
|
A workspace is identified by its name. Basically, you could think of
|
2010-03-21 01:50:10 +01:00
|
|
|
|
workspaces as different desks in your office, if you like the desktop
|
2011-11-25 00:49:20 +01:00
|
|
|
|
metaphor. They just contain different sets of windows and are completely
|
2009-12-07 16:58:46 +01:00
|
|
|
|
separate of each other. Other window managers also call this ``Virtual
|
|
|
|
|
desktops''.
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
=== Split container
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
A split container is a container which holds an arbitrary amount of split
|
|
|
|
|
containers or X11 window containers. It has an orientation (horizontal or
|
|
|
|
|
vertical) and a layout.
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
Split containers (and X11 window containers, which are a subtype of split
|
|
|
|
|
containers) can have different border styles.
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
=== X11 window container
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2011-08-17 02:26:35 +02:00
|
|
|
|
An X11 window container holds exactly one X11 window. These are the leaf nodes
|
|
|
|
|
of the layout tree, they cannot have any children.
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2009-03-07 00:50:18 +01:00
|
|
|
|
== List/queue macros
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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 expected
|
|
|
|
|
features, i3 comes with `include/queue.h`. On BSD systems, you can use man
|
|
|
|
|
`queue(3)`. On Linux, you have to use google (or read the source).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 17:05:28 +02:00
|
|
|
|
The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular
|
|
|
|
|
queues) and +TAILQ+ (tail queues). Usually, only forward traversal is necessary,
|
2009-12-07 16:58:46 +01:00
|
|
|
|
so an `SLIST` works fine. If inserting elements at arbitrary positions or at
|
2011-07-27 17:05:28 +02:00
|
|
|
|
the end of a list is necessary, a +TAILQ+ is used instead. 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
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
There is a row of standard variables used in many events. The following names
|
|
|
|
|
should be chosen for those:
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
* ``conn'' is the xcb_connection_t
|
|
|
|
|
* ``event'' is the event of the particular type
|
2011-07-27 17:05:28 +02:00
|
|
|
|
* ``con'' names a container
|
|
|
|
|
* ``current'' is a loop variable when using +TAILQ_FOREACH+ etc.
|
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
|
2011-07-27 17:05:28 +02:00
|
|
|
|
* Check for XKB extension on the separate X connection, load Xcursor
|
|
|
|
|
* Check for RandR screens (with a fall-back to Xinerama)
|
2009-03-07 00:50:18 +01:00
|
|
|
|
* Grab the keycodes for which bindings exist
|
|
|
|
|
* Manage all existing windows
|
|
|
|
|
* Enter the event loop
|
|
|
|
|
|
|
|
|
|
== Keybindings
|
|
|
|
|
|
|
|
|
|
=== Grabbing the bindings
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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
|
2010-03-21 01:50:10 +01:00
|
|
|
|
therefore need to grab the keycode itself without any modifiers. This means,
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
=== Handling a keypress
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets
|
|
|
|
|
the correct state.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
Then, it looks through all bindings and gets the one which matches the received
|
|
|
|
|
event.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 17:05:28 +02:00
|
|
|
|
The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
|
|
|
|
|
+src/cmdparse.y+.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-27 17:05:28 +02:00
|
|
|
|
== Manage windows (src/main.c, manage_window() and reparent_window())
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +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
|
2009-12-07 16:58:46 +01:00
|
|
|
|
* 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 (see
|
|
|
|
|
`reparent_window()`) if it wasn’t already managed.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +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 differently, they don’t have decorations and are not
|
|
|
|
|
assigned to a specific container. Instead, they are positioned at the bottom
|
2011-11-25 00:49:20 +01:00
|
|
|
|
or top of the screen (in the appropriate dock area containers). To get the
|
|
|
|
|
height which needs to be reserved for the window, the `_NET_WM_STRUT_PARTIAL`
|
|
|
|
|
property is used.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
Furthermore, the list of assignments (to other workspaces, which may be on
|
|
|
|
|
other screens) is checked. If the window matches one of the user’s criteria,
|
|
|
|
|
it may either be put in floating mode or moved to a different workspace. If the
|
|
|
|
|
target workspace is not visible, the window will not be mapped.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== What happens when an application is started?
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
i3 does not care for applications. All it notices is when new windows are
|
|
|
|
|
mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
|
|
|
|
|
reparented (see section "Manage windows").
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-31 16:19:25 +02:00
|
|
|
|
After reparenting the window, `render_tree()` is called which renders the
|
2009-12-07 16:58:46 +01:00
|
|
|
|
internal layout table. The new window has been placed in the currently focused
|
|
|
|
|
container and therefore the new window and the old windows (if any) need to be
|
2010-03-12 18:17:27 +01:00
|
|
|
|
moved/resized so that the currently active layout (default/stacking/tabbed mode)
|
2009-12-07 16:58:46 +01:00
|
|
|
|
is rendered correctly. To move/resize windows, a window is ``configured'' in
|
|
|
|
|
X11-speak.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2010-03-21 01:50:10 +01:00
|
|
|
|
Some applications, such as MPlayer obviously assume the window manager is
|
2009-12-07 16:58:46 +01:00
|
|
|
|
stupid and try to configure their windows by themselves. This generates an
|
|
|
|
|
event called configurerequest. i3 handles these events and tells the window the
|
|
|
|
|
size it had before the configurerequest (with the exception of not yet mapped
|
|
|
|
|
windows, which get configured like they want to, and floating windows, which
|
|
|
|
|
can reconfigure themselves).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== _NET_WM_STATE
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
|
|
|
|
|
``toggle_fullscreen()'' for the 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.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== WM_NAME
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
When the WM_NAME property of a window changes, its decoration (containing the
|
|
|
|
|
title) is re-rendered. Note that WM_NAME is in COMPOUND_TEXT encoding which is
|
|
|
|
|
totally uncommon and cumbersome. Therefore, the _NET_WM_NAME atom will be used
|
|
|
|
|
if present.
|
|
|
|
|
|
|
|
|
|
== _NET_WM_NAME
|
|
|
|
|
|
|
|
|
|
Like WM_NAME, this atom contains the title of a window. However, _NET_WM_NAME
|
|
|
|
|
is encoded in UTF-8. i3 will recode it to UCS-2 in order to be able to pass it
|
|
|
|
|
to X. Using an appropriate font (ISO-10646), you can see most special
|
|
|
|
|
characters (every special character contained in your font).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
|
|
|
|
== Size hints
|
|
|
|
|
|
2010-03-21 01:50:10 +01:00
|
|
|
|
Size hints specify the minimum/maximum size for a given window as well as its
|
2009-12-07 16:58:46 +01:00
|
|
|
|
aspect ratio. This is important for clients like mplayer, who only set the
|
|
|
|
|
aspect ratio and resize their window to be as small as possible (but only with
|
|
|
|
|
some video outputs, for example in Xv, while when using x11, mplayer does the
|
|
|
|
|
necessary centering for itself).
|
2009-04-30 17:27:58 +02:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
So, when an aspect ratio was specified, i3 adjusts the height of the window
|
|
|
|
|
until the size maintains the correct aspect ratio. For the code to do this, see
|
|
|
|
|
src/layout.c, function resize_client().
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
|
|
|
|
== Rendering (src/layout.c, render_layout() and render_container())
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-08-17 10:57:39 +02:00
|
|
|
|
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.
|
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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+.
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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.
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
=== Rendering the root container
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-25 00:49:20 +01:00
|
|
|
|
The i3 root container (`con->type == CT_ROOT`) represents the X11 root window.
|
2011-11-23 00:54:54 +01:00
|
|
|
|
It contains one child container for every output (like LVDS1, VGA1, …), which
|
|
|
|
|
is available on your computer.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
=== Rendering an output
|
|
|
|
|
|
2011-11-25 00:49:20 +01:00
|
|
|
|
Output containers (`con->layout == L_OUTPUT`) represent a hardware output like
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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:
|
|
|
|
|
|
2011-11-25 00:49:20 +01:00
|
|
|
|
1. Find the content container (`con->type == CT_CON`)
|
2011-11-23 00:54:54 +01:00
|
|
|
|
2. Get the currently visible workspace (+con_get_fullscreen_con(content,
|
|
|
|
|
CF_OUTPUT)+).
|
|
|
|
|
3. If there is a fullscreened window on that workspace, directly render it and
|
|
|
|
|
return, thus ignoring the dock areas.
|
|
|
|
|
4. Sum up the space used by all the dock windows (they have a variable height
|
|
|
|
|
only).
|
|
|
|
|
5. Set the workspace rects (x/y/width/height) based on the position of the
|
2011-11-25 00:49:20 +01:00
|
|
|
|
output (stored in `con->rect`) and the usable space
|
|
|
|
|
(`con->rect.{width,height}` without the space used for dock windows).
|
2011-11-23 00:54:54 +01:00
|
|
|
|
6. Recursively raise and render the output’s child containers (meaning dock
|
|
|
|
|
area containers and the content container).
|
|
|
|
|
|
|
|
|
|
=== Rendering a workspace or split container
|
|
|
|
|
|
|
|
|
|
From here on, there really is no difference anymore. All containers are of
|
2011-11-25 00:49:20 +01:00
|
|
|
|
`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
|
2011-11-23 00:54:54 +01:00
|
|
|
|
split container.
|
|
|
|
|
|
|
|
|
|
==== Default layout
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
In default layout, containers are placed horizontally or vertically next to
|
2011-11-25 00:49:20 +01:00
|
|
|
|
each other (depending on the `con->orientation`). If a child is a leaf node (as
|
2011-11-23 00:54:54 +01:00
|
|
|
|
opposed to a split container) and has border style "normal", appropriate space
|
|
|
|
|
will be reserved for its window decoration.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
==== Stacked layout
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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()+).
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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.
|
2009-12-07 16:58:46 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
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.
|
2009-12-07 16:58:46 +01:00
|
|
|
|
|
2011-11-23 00:54:54 +01:00
|
|
|
|
==== 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).
|
|
|
|
|
|
|
|
|
|
==== Dock area layout
|
|
|
|
|
|
|
|
|
|
This is a special case. Users cannot chose 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.
|
|
|
|
|
|
|
|
|
|
=== Rendering a window
|
|
|
|
|
|
|
|
|
|
A window’s size and position will be determined in the following way:
|
|
|
|
|
|
|
|
|
|
1. Subtract the border if border style is not "none" (but "normal" or "1pixel").
|
|
|
|
|
2. Subtract the X11 border, if the window has an X11 border > 0.
|
|
|
|
|
3. Obey the aspect ratio of the window (think MPlayer).
|
|
|
|
|
4. Obey the height- and width-increments of the window (think terminal emulator
|
|
|
|
|
which can only be resized in one-line or one-character steps).
|
|
|
|
|
|
|
|
|
|
== Pushing updates to X11 / Drawing
|
|
|
|
|
|
2011-11-23 22:54:03 +01:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
1. The state pushing: +x_push_changes()+, which calls +x_push_node()+.
|
|
|
|
|
2. 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+.
|
|
|
|
|
3. Expose event handling (drawing decorations): +x_deco_recurse()+ and
|
|
|
|
|
+x_draw_decoration()+.
|
|
|
|
|
|
|
|
|
|
=== 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:
|
|
|
|
|
|
|
|
|
|
1. 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.
|
|
|
|
|
2. 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.
|
|
|
|
|
3. +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.
|
|
|
|
|
4. 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.
|
|
|
|
|
5. The eventmask is restored for all mapped windows.
|
|
|
|
|
6. Window decorations will be rendered by calling +x_deco_recurse+ on the root
|
|
|
|
|
container, which then recursively calls itself for the children.
|
|
|
|
|
7. If the input focus needs to be changed (because the user focused a different
|
|
|
|
|
window), it will be updated now.
|
|
|
|
|
8. +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:
|
|
|
|
|
|
|
|
|
|
1. Update the window’s +WM_NAME+, if changed (the +WM_NAME+ is set on i3
|
|
|
|
|
containers mainly for debugging purposes).
|
|
|
|
|
2. Reparents a child window into the i3 container if the container was created
|
|
|
|
|
for a specific managed window.
|
|
|
|
|
3. 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).
|
|
|
|
|
4. Size/position for the child window is adjusted.
|
|
|
|
|
5. 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.
|
|
|
|
|
6. +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+.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
=== 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 at the moment just says
|
|
|
|
|
"another container").
|
|
|
|
|
|
|
|
|
|
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.
|
2011-11-23 00:54:54 +01:00
|
|
|
|
|
|
|
|
|
|
2011-11-23 22:54:03 +01:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-11-23 22:54:03 +01:00
|
|
|
|
== Resizing containers
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
* A new, invisible window with the size of the root window is created
|
|
|
|
|
(+grabwin+)
|
|
|
|
|
* 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
|
2010-03-21 01:50:10 +01:00
|
|
|
|
it is only there to inform the user how big the container will be (it
|
2009-12-07 16:58:46 +01:00
|
|
|
|
creates the impression of dragging the border out of the container).
|
|
|
|
|
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer
|
2010-03-21 01:50:10 +01:00
|
|
|
|
and enter its own event loop which will pass all events (expose events) but
|
2009-12-07 16:58:46 +01:00
|
|
|
|
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.
|
|
|
|
|
* The new width_factor for each involved column (respectively row) will be
|
|
|
|
|
calculated.
|
2009-03-07 00:50:18 +01:00
|
|
|
|
|
2011-07-31 16:19:25 +02:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2012-01-16 23:44:28 +01:00
|
|
|
|
== User commands (parser-specs/commands.spec)
|
|
|
|
|
|
|
|
|
|
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 link:userguide.html[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
|
2012-01-17 00:41:24 +01:00
|
|
|
|
command language. Therefore, starting from version 4.2, we use a custom parser
|
|
|
|
|
for user commands (not yet for the configuration file).
|
2012-01-16 23:44:28 +01:00
|
|
|
|
The input specification for this parser can be found in the file
|
|
|
|
|
+parser-specs/commands.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).
|
|
|
|
|
|
|
|
|
|
=== 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>
|
|
|
|
|
state WORKSPACE:
|
|
|
|
|
direction = 'next_on_output', 'prev_on_output', 'next', 'prev'
|
|
|
|
|
-> call cmd_workspace($direction)
|
|
|
|
|
'back_and_forth'
|
|
|
|
|
-> call cmd_workspace_back_and_forth()
|
|
|
|
|
workspace = string
|
|
|
|
|
-> call cmd_workspace_name($workspace)
|
|
|
|
|
----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
As you can see from the commands, there are multiple different valid variants
|
|
|
|
|
of the workspace command:
|
|
|
|
|
|
|
|
|
|
workspace <direction>::
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
workspace back_and_forth::
|
|
|
|
|
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).
|
|
|
|
|
workspace <name>::
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
=== 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):
|
|
|
|
|
|
|
|
|
|
1. Define a function beginning with +cmd_+ in the file +src/commands.c+. Copy
|
|
|
|
|
the prototype of an existing function.
|
|
|
|
|
2. 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.
|
|
|
|
|
3. Write a test case (or extend an existing test case) for your feature, see
|
|
|
|
|
link:testsuite.html[i3 testsuite]. For now, it is sufficient to simply call
|
|
|
|
|
your command in all the various possible ways.
|
|
|
|
|
4. 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.
|
|
|
|
|
5. Actually implement the feature.
|
|
|
|
|
6. Document the feature in the link:userguide.html[User’s Guide].
|
2011-07-31 16:19:25 +02:00
|
|
|
|
|
2011-02-19 21:45:57 +01:00
|
|
|
|
== 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.
|
|
|
|
|
|
|
|
|
|
=== 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).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[width="15%",cols="^,^"]
|
|
|
|
|
|========
|
|
|
|
|
| 1 | 2
|
|
|
|
|
|========
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
=== 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).
|
|
|
|
|
|
|
|
|
|
[width="15%",cols="^,^"]
|
|
|
|
|
|========
|
|
|
|
|
1.2+^.^| 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.
|
|
|
|
|
|
|
|
|
|
=== 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:
|
|
|
|
|
|
|
|
|
|
[width="15%",cols="^,^"]
|
|
|
|
|
|========
|
|
|
|
|
| 1 | 2
|
|
|
|
|
|========
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
=== 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).
|
|
|
|
|
|
|
|
|
|
[width="15%",cols="^,^"]
|
|
|
|
|
|========
|
|
|
|
|
2+| 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.
|
|
|
|
|
|
|
|
|
|
=== 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)):
|
|
|
|
|
|
|
|
|
|
[width="15%",cols="^,^"]
|
|
|
|
|
|========
|
|
|
|
|
| 1 1.2+^.^| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
=== 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+.
|
|
|
|
|
|
2011-03-05 20:35:16 +01:00
|
|
|
|
== 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
|
|
|
|
|
|
2009-03-07 06:24:31 +01:00
|
|
|
|
== Gotchas
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
* 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.
|
2009-03-07 06:24:31 +01:00
|
|
|
|
|
2012-01-29 20:15:18 +01:00
|
|
|
|
* 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.
|
|
|
|
|
|
2009-04-30 17:27:58 +02:00
|
|
|
|
== Using git / sending patches
|
|
|
|
|
|
2009-12-07 16:58:46 +01:00
|
|
|
|
For a short introduction into using git, see
|
2013-03-14 14:43:51 +01:00
|
|
|
|
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
|
2009-04-30 17:27:58 +02:00
|
|
|
|
|
2013-02-10 15:10:13 +01:00
|
|
|
|
Please talk to us before working on new features to see whether they will be
|
|
|
|
|
accepted. There are a few things which we don’t want to see in i3, e.g. a
|
|
|
|
|
command which will focus windows in an alt+tab like way.
|
2009-04-30 17:27:58 +02:00
|
|
|
|
|
2013-02-10 15:10:13 +01:00
|
|
|
|
When working on bugfixes, please make sure you mention that you are working on
|
|
|
|
|
it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no
|
|
|
|
|
bugreport yet, please create one.
|
2009-04-30 17:27:58 +02:00
|
|
|
|
|
2013-02-10 15:10:13 +01:00
|
|
|
|
After you are done, please submit your work for review at http://cr.i3wm.org/
|
2009-04-30 17:27:58 +02:00
|
|
|
|
|
2013-02-10 15:10:13 +01:00
|
|
|
|
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
|
|
|
|
|
http://cr.i3wm.org/. 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.
|
2011-08-27 19:36:07 +02:00
|
|
|
|
|
|
|
|
|
== Thought experiments
|
|
|
|
|
|
|
|
|
|
In this section, we collect thought experiments, so that we don’t forget our
|
|
|
|
|
thoughts about specific topics. They are not necessary to get into hacking i3,
|
|
|
|
|
but if you are interested in one of the topics they cover, you should read them
|
|
|
|
|
before asking us why things are the way they are or why we don’t implement
|
|
|
|
|
things.
|
|
|
|
|
|
|
|
|
|
=== Using cgroups per workspace
|
|
|
|
|
|
|
|
|
|
cgroups (control groups) are a linux-only feature which provides the ability to
|
|
|
|
|
group multiple processes. For each group, you can individually set resource
|
|
|
|
|
limits, like allowed memory usage. Furthermore, and more importantly for our
|
|
|
|
|
purposes, they serve as a namespace, a label which you can attach to processes
|
|
|
|
|
and their children.
|
|
|
|
|
|
|
|
|
|
One interesting use for cgroups is having one cgroup per workspace (or
|
|
|
|
|
container, doesn’t really matter). That way, you could set different priorities
|
|
|
|
|
and have a workspace for important stuff (say, writing a LaTeX document or
|
|
|
|
|
programming) and a workspace for unimportant background stuff (say,
|
|
|
|
|
JDownloader). Both tasks can obviously consume a lot of I/O resources, but in
|
|
|
|
|
this example it doesn’t really matter if JDownloader unpacks the download a
|
|
|
|
|
minute earlier or not. However, your compiler should work as fast as possible.
|
|
|
|
|
Having one cgroup per workspace, you would assign more resources to the
|
|
|
|
|
programming workspace.
|
|
|
|
|
|
|
|
|
|
Another interesting feature is that an inherent problem of the workspace
|
|
|
|
|
concept could be solved by using cgroups: When starting an application on
|
|
|
|
|
workspace 1, then switching to workspace 2, you will get the application’s
|
|
|
|
|
window(s) on workspace 2 instead of the one you started it on. This is because
|
|
|
|
|
the window manager does not have any mapping between the process it starts (or
|
|
|
|
|
gets started in any way) and the window(s) which appear.
|
|
|
|
|
|
|
|
|
|
Imagine for example using dmenu: The user starts dmenu by pressing Mod+d, dmenu
|
|
|
|
|
gets started with PID 3390. The user then decides to launch Firefox, which
|
|
|
|
|
takes a long time. So he enters firefox into dmenu and presses enter. Firefox
|
|
|
|
|
gets started with PID 4001. When it finally finishes loading, it creates an X11
|
|
|
|
|
window and uses MapWindow to make it visible. This is the first time i3
|
|
|
|
|
actually gets in touch with Firefox. It decides to map the window, but it has
|
|
|
|
|
no way of knowing that this window (even though it has the _NET_WM_PID property
|
|
|
|
|
set to 4001) belongs to the dmenu the user started before.
|
|
|
|
|
|
|
|
|
|
How do cgroups help with this? Well, when pressing Mod+d to launch dmenu, i3
|
|
|
|
|
would create a new cgroup, let’s call it i3-3390-1. It launches dmenu in that
|
|
|
|
|
cgroup, which gets PID 3390. As before, the user enters firefox and Firefox
|
|
|
|
|
gets launched with PID 4001. This time, though, the Firefox process with PID
|
|
|
|
|
4001 is *also* member of the cgroup i3-3390-1 (because fork()ing in a cgroup
|
|
|
|
|
retains the cgroup property). Therefore, when mapping the window, i3 can look
|
|
|
|
|
up in which cgroup the process is and can establish a mapping between the
|
|
|
|
|
workspace and the window.
|
|
|
|
|
|
|
|
|
|
There are multiple problems with this approach:
|
|
|
|
|
|
|
|
|
|
. Every application has to properly set +_NET_WM_PID+. This is acceptable and
|
|
|
|
|
patches can be written for the few applications which don’t set the hint yet.
|
|
|
|
|
. It does only work on Linux, since cgroups are a Linux-only feature. Again,
|
|
|
|
|
this is acceptable.
|
|
|
|
|
. The main problem is that some applications create X11 windows completely
|
|
|
|
|
independent of UNIX processes. An example for this is Chromium (or
|
|
|
|
|
gnome-terminal), which, when being started a second time, communicates with
|
|
|
|
|
the first process and lets the first process open a new window. Therefore, if
|
|
|
|
|
you have a Chromium window on workspace 2 and you are currently working on
|
|
|
|
|
workspace 3, starting +chromium+ does not lead to the desired result (the
|
|
|
|
|
window will open on workspace 2).
|
|
|
|
|
|
|
|
|
|
Therefore, my conclusion is that the only proper way of fixing the "window gets
|
|
|
|
|
opened on the wrong workspace" problem is in the application itself. Most
|
|
|
|
|
modern applications support freedesktop startup-notifications which can be
|
|
|
|
|
used for this.
|