4 * i3 - an improved dynamic tiling window manager
6 * © 2009 Michael Stapelberg and contributors
8 * See file LICENSE for license information.
15 #include <sys/types.h>
21 #include <X11/XKBlib.h>
22 #include <X11/extensions/XKB.h>
25 #include <xcb/xcb_wm.h>
26 #include <xcb/xcb_aux.h>
27 #include <xcb/xcb_event.h>
28 #include <xcb/xcb_property.h>
29 #include <xcb/xcb_keysyms.h>
30 #include <xcb/xcb_icccm.h>
31 #include <xcb/xinerama.h>
45 /* This is the path to i3, copied from argv[0] when starting up */
46 char *application_path;
48 /* This is our connection to X11 for use with XKB */
51 /* The list of key bindings */
52 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
54 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
55 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
57 /* The event handlers need to be global because they are accessed by our custom event handler
58 in handle_button_press(), needed for graphical resizing */
59 xcb_event_handlers_t evenths;
60 xcb_atom_t atoms[NUM_ATOMS];
65 * Do some sanity checks and then reparent the window.
68 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
69 LOG("managing window.\n");
70 xcb_drawable_t d = { window };
71 xcb_get_geometry_cookie_t geomc;
72 xcb_get_geometry_reply_t *geom;
73 xcb_get_window_attributes_reply_t *attr = 0;
75 if (wa.tag == TAG_COOKIE) {
76 /* Check if the window is mapped (it could be not mapped when intializing and
77 calling manage_window() for every window) */
78 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
81 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
85 wa.u.override_redirect = attr->override_redirect;
88 /* Don’t manage clients with the override_redirect flag */
89 if (wa.u.override_redirect)
92 /* Check if the window is already managed */
93 if (table_get(byChild, window))
96 /* Get the initial geometry (position, size, …) */
97 geomc = xcb_get_geometry(conn, d);
100 wa.u.cookie = xcb_get_window_attributes(conn, window);
101 attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
103 geom = xcb_get_geometry_reply(conn, geomc, 0);
105 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
106 geom->x, geom->y, geom->width, geom->height);
107 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
117 * reparent_window() gets called when a new window was opened and becomes a child of the root
118 * window, or it gets called by us when we manage the already existing windows at startup.
120 * Essentially, this is the point where we take over control.
123 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
124 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
125 int16_t x, int16_t y, uint16_t width, uint16_t height) {
127 xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
129 /* Place requests for properties ASAP */
130 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
131 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
133 Client *new = table_get(byChild, child);
135 /* Events for already managed windows should already be filtered in manage_window() */
138 LOG("reparenting new client\n");
139 new = calloc(sizeof(Client), 1);
140 new->force_reconfigure = true;
144 /* Update the data structures */
145 Client *old_focused = CUR_CELL->currently_focused;
147 new->container = CUR_CELL;
149 new->frame = xcb_generate_id(conn);
151 new->rect.width = width;
152 new->rect.height = height;
154 /* Don’t generate events for our new window, it should *not* be managed */
155 mask |= XCB_CW_OVERRIDE_REDIRECT;
158 /* We want to know when… */
159 mask |= XCB_CW_EVENT_MASK;
160 values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */
161 XCB_EVENT_MASK_BUTTON_RELEASE |
162 XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */
163 XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */
165 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
167 i3Font *font = load_font(conn, config.font);
168 width = min(width, c_ws->rect.x + c_ws->rect.width);
169 height = min(height, c_ws->rect.y + c_ws->rect.height);
171 Rect framerect = {x, y,
172 width + 2 + 2, /* 2 px border at each side */
173 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
175 /* Yo dawg, I heard you like windows, so I create a window around your window… */
176 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
178 /* Put the client inside the save set. Upon termination (whether killed or normal exit
179 does not matter) of the window manager, these clients will be correctly reparented
180 to their most closest living ancestor (= cleanup) */
181 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
183 /* Generate a graphics context for the titlebar */
184 new->titlegc = xcb_generate_id(conn);
185 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
187 /* Moves the original window into the new frame we've created for it */
188 new->awaiting_useless_unmap = true;
189 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
190 if (xcb_request_check(conn, cookie) != NULL) {
191 LOG("Could not reparent the window, aborting\n");
192 xcb_destroy_window(conn, new->frame);
197 /* We are interested in property changes */
198 mask = XCB_CW_EVENT_MASK;
199 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
200 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
201 XCB_EVENT_MASK_ENTER_WINDOW;
202 cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
203 if (xcb_request_check(conn, cookie) != NULL) {
204 LOG("Could not change window attributes, aborting\n");
205 xcb_destroy_window(conn, new->frame);
210 /* Put our data structure (Client) into the table */
211 table_put(byParent, new->frame, new);
212 table_put(byChild, child, new);
214 /* We need to grab the mouse buttons for click to focus */
215 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
216 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
217 1 /* left mouse button */,
218 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
220 /* Focus the new window if we’re not in fullscreen mode */
221 if (CUR_CELL->workspace->fullscreen_client == NULL) {
222 CUR_CELL->currently_focused = new;
223 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
225 /* If we are in fullscreen, we should lower the window to not be annoying */
226 uint32_t values[] = { XCB_STACK_MODE_BELOW };
227 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
230 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
232 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
233 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
234 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
235 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
236 LOG("Window is a dock.\n");
238 new->titlebar_position = TITLEBAR_OFF;
239 new->force_reconfigure = true;
240 new->container = NULL;
241 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
245 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
247 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
248 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
249 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
250 of the screen. This is because the only possibility for bars is at to be at the top/bottom
251 with maximum horizontal size.
252 TODO: bars at the top */
253 new->desired_height = strut[3];
254 LOG("the client wants to be %d pixels height\n", new->desired_height);
257 /* Insert into the currently active container, if it’s not a dock window */
259 /* Insert after the old active client, if existing. If it does not exist, the
260 container is empty and it does not matter, where we insert it */
261 if (old_focused != NULL)
262 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
263 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
265 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
272 * Go through all existing windows (if the window manager is restarted) and manage them
275 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
276 xcb_query_tree_reply_t *reply;
278 xcb_window_t *children;
279 xcb_get_window_attributes_cookie_t *cookies;
281 /* Get the tree of windows whose parent is the root window (= all) */
282 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
285 len = xcb_query_tree_children_length(reply);
286 cookies = smalloc(len * sizeof(*cookies));
288 /* Request the window attributes for every window */
289 children = xcb_query_tree_children(reply);
290 for(i = 0; i < len; ++i)
291 cookies[i] = xcb_get_window_attributes(conn, children[i]);
293 /* Call manage_window with the attributes for every window */
294 for(i = 0; i < len; ++i) {
295 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
296 manage_window(prophs, conn, children[i], wa);
303 int main(int argc, char *argv[], char *env[]) {
305 char *override_configpath = NULL;
306 xcb_connection_t *conn;
307 xcb_property_handlers_t prophs;
309 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
311 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
312 if (!isatty(fileno(stdout)))
313 setbuf(stdout, NULL);
315 application_path = sstrdup(argv[0]);
317 while ((opt = getopt(argc, argv, "c:")) != -1) {
320 override_configpath = sstrdup(optarg);
323 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
328 /* Initialize the table data structures for each workspace */
331 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
332 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
334 byChild = alloc_table();
335 byParent = alloc_table();
337 load_configuration(override_configpath);
339 conn = xcb_connect(NULL, &screens);
341 /* Place requests for the atoms we need as soon as possible */
342 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
344 REQUEST_ATOM(_NET_SUPPORTED);
345 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
346 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
347 REQUEST_ATOM(_NET_WM_NAME);
348 REQUEST_ATOM(_NET_WM_STATE);
349 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
350 REQUEST_ATOM(_NET_WM_DESKTOP);
351 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
352 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
353 REQUEST_ATOM(UTF8_STRING);
355 /* TODO: this has to be more beautiful somewhen */
356 int major, minor, error;
358 major = XkbMajorVersion;
359 minor = XkbMinorVersion;
363 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
364 fprintf(stderr, "XkbOpenDisplay() failed\n");
369 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
370 fprintf(stderr, "XKB not supported by X-server\n");
373 /* end of ugliness */
375 xcb_event_handlers_init(conn, &evenths);
377 /* DEBUG: Trap all events and print them */
378 for (i = 2; i < 128; ++i)
379 xcb_event_set_handler(&evenths, i, handle_event, 0);
381 for (i = 0; i < 256; ++i)
382 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
384 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
385 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
387 /* Key presses/releases are pretty obvious, I think */
388 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
389 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
391 /* Enter window = user moved his mouse over the window */
392 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
394 /* Button press = user pushed a mouse button over one of our windows */
395 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
397 /* Map notify = there is a new window */
398 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
400 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
401 it any longer. Usually, the client destroys the window shortly afterwards. */
402 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
404 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
405 it to set up ignore the following enter_notify events */
406 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, 0);
408 /* Client message = client changed its properties (EWMH) */
409 /* TODO: can’t we do this via property handlers? */
410 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
412 /* Initialize the property handlers */
413 xcb_property_handlers_init(&prophs, &evenths);
415 /* Watch the WM_NAME (= title of the window) property */
416 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
418 /* Watch size hints (to obey correct aspect ratio) */
419 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
421 /* Get the root window and set the event mask */
422 root = xcb_aux_get_screen(conn, screens)->root;
424 uint32_t mask = XCB_CW_EVENT_MASK;
425 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
426 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
427 projector), the root window gets a
429 XCB_EVENT_MASK_PROPERTY_CHANGE |
430 XCB_EVENT_MASK_ENTER_WINDOW };
431 xcb_change_window_attributes(conn, root, mask, values);
433 /* Setup NetWM atoms */
434 #define GET_ATOM(name) { \
435 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
437 printf("Could not get atom " #name "\n"); \
440 atoms[name] = reply->atom; \
444 GET_ATOM(_NET_SUPPORTED);
445 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
446 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
447 GET_ATOM(_NET_WM_NAME);
448 GET_ATOM(_NET_WM_STATE);
449 GET_ATOM(_NET_WM_WINDOW_TYPE);
450 GET_ATOM(_NET_WM_DESKTOP);
451 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
452 GET_ATOM(_NET_WM_STRUT_PARTIAL);
453 GET_ATOM(UTF8_STRING);
455 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
456 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
458 /* Set up the atoms we support */
459 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
460 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
461 /* Set up the window manager’s name */
462 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
463 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
465 /* Grab the bound keys */
467 TAILQ_FOREACH(bind, &bindings, bindings) {
468 LOG("Grabbing %d\n", bind->keycode);
469 if (bind->mods & BIND_MODE_SWITCH)
470 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
471 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
474 /* check for Xinerama */
475 LOG("Checking for Xinerama...\n");
476 initialize_xinerama(conn);
480 manage_existing_windows(conn, &prophs, root);
482 /* Get pointer position to see on which screen we’re starting */
483 xcb_query_pointer_reply_t *reply;
484 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
485 LOG("Could not get pointer position\n");
489 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
490 if (screen == NULL) {
491 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
494 if (screen->current_workspace != 0) {
495 LOG("Ok, I need to go to the other workspace\n");
496 c_ws = &workspaces[screen->current_workspace];
499 /* Enter xcb’s event handler */
500 xcb_event_wait_for_event_loop(&evenths);