diff --git a/docs/hacking-howto b/docs/hacking-howto new file mode 100644 index 00000000..d2ba44fa --- /dev/null +++ b/docs/hacking-howto @@ -0,0 +1,275 @@ +Hacking i3: How To +================== +Michael Stapelberg +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 +. Handle the client’s _WM_STATE property, but only the _WM_STATE_FULLSCREEN +. Handle the client’s WM_NAME property +. 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 + +See include/data.h for documented data structures. + +=== Virtual screens + +A virtual screen (type i3Screen) is generated from the connected screens obtained +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 +1024x768, set up in clone mode (xrandr --output VGA --mode 1024x768 --same-as LVDS), +i3 will have one virtual screen. + +However, if you configure it using xrandr --output VGA --mode 1024x768 --right-of LVDS, +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. + +== 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, +i3 comes with include/queue.h. On BSD systems, you can use man queue(3). On Linux, +you have to use google. + +The lists used are SLISTs (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 +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: + + * "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 + +== Startup (src/mainx.c) + + * 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()) + +manage_window() does some checks to decide whether the window should be managed at all: + + * 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"). + +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 of the screen. To get the height which needsd +to be reserved for the window, the _NET_WM_STRUT_PARTIAL property is used. + +== What happens when an application is started? + +i3 does not care for applications. All it notices is when new windows are mapped (see +src/handlers.c, handle_map_notify_event()). The window is then reparented (see section +"Manage windows"). + +After reparenting the window, render_layout() is called which renders the internal +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 +correctly. To move/resize windows, a window is "configured" in X11-speak. + +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 + +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. + +== WM_NAME + +When the WM_NAME property of a window changes, its decoration (containing the title) +is re-rendered. + +== Size hints + +== Rendering + +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. + +=== 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: + +exec:: +Starts the given command by passing it to /bin/sh. + +restart:: +Restarts i3 by executing argv[0] (the path with which you started i3) without forking. + +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.