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>
22 #include <X11/XKBlib.h>
23 #include <X11/extensions/XKB.h>
26 #include <xcb/xcb_atom.h>
27 #include <xcb/xcb_aux.h>
28 #include <xcb/xcb_event.h>
29 #include <xcb/xcb_property.h>
30 #include <xcb/xcb_keysyms.h>
31 #include <xcb/xcb_icccm.h>
32 #include <xcb/xinerama.h>
46 /* This is the path to i3, copied from argv[0] when starting up */
49 /* This is our connection to X11 for use with XKB */
52 /* The list of key bindings */
53 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
55 /* The list of exec-lines */
56 struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
58 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
59 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
61 /* The event handlers need to be global because they are accessed by our custom event handler
62 in handle_button_press(), needed for graphical resizing */
63 xcb_event_handlers_t evenths;
64 xcb_atom_t atoms[NUM_ATOMS];
69 * Do some sanity checks and then reparent the window.
72 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
73 LOG("managing window.\n");
74 xcb_drawable_t d = { window };
75 xcb_get_geometry_cookie_t geomc;
76 xcb_get_geometry_reply_t *geom;
77 xcb_get_window_attributes_reply_t *attr = 0;
79 if (wa.tag == TAG_COOKIE) {
80 /* Check if the window is mapped (it could be not mapped when intializing and
81 calling manage_window() for every window) */
82 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
85 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
89 wa.u.override_redirect = attr->override_redirect;
92 /* Don’t manage clients with the override_redirect flag */
93 if (wa.u.override_redirect)
96 /* Check if the window is already managed */
97 if (table_get(&by_child, window))
100 /* Get the initial geometry (position, size, …) */
101 geomc = xcb_get_geometry(conn, d);
104 wa.u.cookie = xcb_get_window_attributes(conn, window);
105 attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
107 geom = xcb_get_geometry_reply(conn, geomc, 0);
109 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
110 geom->x, geom->y, geom->width, geom->height);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
112 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
113 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
114 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
124 * reparent_window() gets called when a new window was opened and becomes a child of the root
125 * window, or it gets called by us when we manage the already existing windows at startup.
127 * Essentially, this is the point where we take over control.
130 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
131 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
132 int16_t x, int16_t y, uint16_t width, uint16_t height) {
134 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
138 /* We are interested in property changes */
139 mask = XCB_CW_EVENT_MASK;
140 values[0] = CHILD_EVENT_MASK;
141 xcb_change_window_attributes(conn, child, mask, values);
143 /* Map the window first to avoid flickering */
144 xcb_map_window(conn, child);
146 /* Place requests for properties ASAP */
147 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
148 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
149 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
151 Client *new = table_get(&by_child, child);
153 /* Events for already managed windows should already be filtered in manage_window() */
156 LOG("reparenting new client\n");
157 new = calloc(sizeof(Client), 1);
158 new->force_reconfigure = true;
160 /* Update the data structures */
161 Client *old_focused = CUR_CELL->currently_focused;
163 new->container = CUR_CELL;
164 new->workspace = new->container->workspace;
166 new->frame = xcb_generate_id(conn);
168 new->rect.width = width;
169 new->rect.height = height;
173 /* Don’t generate events for our new window, it should *not* be managed */
174 mask |= XCB_CW_OVERRIDE_REDIRECT;
177 /* We want to know when… */
178 mask |= XCB_CW_EVENT_MASK;
179 values[1] = FRAME_EVENT_MASK;
181 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
183 i3Font *font = load_font(conn, config.font);
184 width = min(width, c_ws->rect.x + c_ws->rect.width);
185 height = min(height, c_ws->rect.y + c_ws->rect.height);
187 Rect framerect = {x, y,
188 width + 2 + 2, /* 2 px border at each side */
189 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
191 /* Yo dawg, I heard you like windows, so I create a window around your window… */
192 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
194 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
195 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
197 /* Put the client inside the save set. Upon termination (whether killed or normal exit
198 does not matter) of the window manager, these clients will be correctly reparented
199 to their most closest living ancestor (= cleanup) */
200 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
202 /* Generate a graphics context for the titlebar */
203 new->titlegc = xcb_generate_id(conn);
204 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
206 /* Moves the original window into the new frame we've created for it */
207 new->awaiting_useless_unmap = true;
208 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
209 if (xcb_request_check(conn, cookie) != NULL) {
210 LOG("Could not reparent the window, aborting\n");
211 xcb_destroy_window(conn, new->frame);
216 /* Put our data structure (Client) into the table */
217 table_put(&by_parent, new->frame, new);
218 table_put(&by_child, child, new);
220 /* We need to grab the mouse buttons for click to focus */
221 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
222 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
223 1 /* left mouse button */,
224 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
226 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
228 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
229 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
230 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
231 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
232 LOG("Window is a dock.\n");
234 new->titlebar_position = TITLEBAR_OFF;
235 new->force_reconfigure = true;
236 new->container = NULL;
237 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
242 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
244 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
245 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
246 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
247 of the screen. This is because the only possibility for bars is at to be at the top/bottom
248 with maximum horizontal size.
249 TODO: bars at the top */
250 new->desired_height = strut[3];
251 if (new->desired_height == 0) {
252 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
253 new->desired_height = height;
255 LOG("the client wants to be %d pixels high\n", new->desired_height);
257 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
258 new->desired_height = height;
262 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
263 if (CUR_CELL->workspace->fullscreen_client == NULL) {
265 CUR_CELL->currently_focused = new;
266 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
269 /* If we are in fullscreen, we should lower the window to not be annoying */
270 uint32_t values[] = { XCB_STACK_MODE_BELOW };
271 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
274 /* Insert into the currently active container, if it’s not a dock window */
276 /* Insert after the old active client, if existing. If it does not exist, the
277 container is empty and it does not matter, where we insert it */
278 if (old_focused != NULL && !old_focused->dock)
279 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
280 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
282 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
285 /* Check if the window already got the fullscreen hint set */
287 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
288 (state = xcb_get_property_value(preply)) != NULL)
289 /* Check all set _NET_WM_STATEs */
290 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
291 if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
292 /* If the window got the fullscreen state, we just toggle fullscreen
293 and don’t event bother to redraw the layout – that would not change
295 toggle_fullscreen(conn, new);
303 * Go through all existing windows (if the window manager is restarted) and manage them
306 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
307 xcb_query_tree_reply_t *reply;
309 xcb_window_t *children;
310 xcb_get_window_attributes_cookie_t *cookies;
312 /* Get the tree of windows whose parent is the root window (= all) */
313 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
316 len = xcb_query_tree_children_length(reply);
317 cookies = smalloc(len * sizeof(*cookies));
319 /* Request the window attributes for every window */
320 children = xcb_query_tree_children(reply);
321 for(i = 0; i < len; ++i)
322 cookies[i] = xcb_get_window_attributes(conn, children[i]);
324 /* Call manage_window with the attributes for every window */
325 for(i = 0; i < len; ++i) {
326 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
327 manage_window(prophs, conn, children[i], wa);
334 int main(int argc, char *argv[], char *env[]) {
336 char *override_configpath = NULL;
337 xcb_connection_t *conn;
338 xcb_property_handlers_t prophs;
340 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
342 setlocale(LC_ALL, "");
344 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
345 if (!isatty(fileno(stdout)))
346 setbuf(stdout, NULL);
350 while ((opt = getopt(argc, argv, "c:v")) != -1) {
353 override_configpath = sstrdup(optarg);
356 printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
359 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
364 LOG("i3 version " I3_VERSION " starting\n");
366 /* Initialize the table data structures for each workspace */
369 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
370 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
372 load_configuration(override_configpath);
374 conn = xcb_connect(NULL, &screens);
376 /* Place requests for the atoms we need as soon as possible */
377 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
379 REQUEST_ATOM(_NET_SUPPORTED);
380 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
381 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
382 REQUEST_ATOM(_NET_WM_NAME);
383 REQUEST_ATOM(_NET_WM_STATE);
384 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
385 REQUEST_ATOM(_NET_WM_DESKTOP);
386 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
387 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
388 REQUEST_ATOM(WM_PROTOCOLS);
389 REQUEST_ATOM(WM_DELETE_WINDOW);
390 REQUEST_ATOM(UTF8_STRING);
391 REQUEST_ATOM(WM_STATE);
393 /* TODO: this has to be more beautiful somewhen */
394 int major, minor, error;
396 major = XkbMajorVersion;
397 minor = XkbMinorVersion;
401 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
402 fprintf(stderr, "XkbOpenDisplay() failed\n");
407 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
408 fprintf(stderr, "XKB not supported by X-server\n");
411 /* end of ugliness */
413 xcb_event_handlers_init(conn, &evenths);
415 /* DEBUG: Trap all events and print them */
416 for (i = 2; i < 128; ++i)
417 xcb_event_set_handler(&evenths, i, handle_event, 0);
419 for (i = 0; i < 256; ++i)
420 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
422 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
423 xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
425 /* Key presses/releases are pretty obvious, I think */
426 xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
427 xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
429 /* Enter window = user moved his mouse over the window */
430 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
432 /* Button press = user pushed a mouse button over one of our windows */
433 xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
435 /* Map notify = there is a new window */
436 xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
438 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
439 it any longer. Usually, the client destroys the window shortly afterwards. */
440 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
442 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
443 it to set up ignore the following enter_notify events */
444 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
446 /* Configure request = window tried to change size on its own */
447 xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
449 /* Client message are sent to the root window. The only interesting client message
450 for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
451 xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
453 /* Initialize the property handlers */
454 xcb_property_handlers_init(&prophs, &evenths);
456 /* Watch size hints (to obey correct aspect ratio) */
457 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
459 /* Get the root window and set the event mask */
460 root = xcb_aux_get_screen(conn, screens)->root;
462 uint32_t mask = XCB_CW_EVENT_MASK;
463 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
464 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
465 projector), the root window gets a
467 XCB_EVENT_MASK_PROPERTY_CHANGE |
468 XCB_EVENT_MASK_ENTER_WINDOW };
469 xcb_change_window_attributes(conn, root, mask, values);
471 /* Setup NetWM atoms */
472 #define GET_ATOM(name) { \
473 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
475 LOG("Could not get atom " #name "\n"); \
478 atoms[name] = reply->atom; \
482 GET_ATOM(_NET_SUPPORTED);
483 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
484 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
485 GET_ATOM(_NET_WM_NAME);
486 GET_ATOM(_NET_WM_STATE);
487 GET_ATOM(_NET_WM_WINDOW_TYPE);
488 GET_ATOM(_NET_WM_DESKTOP);
489 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
490 GET_ATOM(_NET_WM_STRUT_PARTIAL);
491 GET_ATOM(WM_PROTOCOLS);
492 GET_ATOM(WM_DELETE_WINDOW);
493 GET_ATOM(UTF8_STRING);
496 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
497 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
499 /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
500 xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
502 /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
503 xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
505 /* Watch WM_CLASS (= class of the window) */
506 xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
508 /* Set up the atoms we support */
509 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
510 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
511 /* Set up the window manager’s name */
512 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
513 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
515 xcb_get_numlock_mask(conn);
517 /* Grab the bound keys */
519 TAILQ_FOREACH(bind, &bindings, bindings) {
520 LOG("Grabbing %d\n", bind->keycode);
521 if (bind->mods & BIND_MODE_SWITCH)
522 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
524 /* Grab the key in all combinations */
525 #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \
526 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
527 GRAB_KEY(bind->mods);
528 GRAB_KEY(bind->mods | xcb_numlock_mask);
529 GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
533 /* Autostarting exec-lines */
535 TAILQ_FOREACH(exec, &autostarts, autostarts) {
536 LOG("auto-starting %s\n", exec->command);
537 start_application(exec->command);
540 /* check for Xinerama */
541 LOG("Checking for Xinerama...\n");
542 initialize_xinerama(conn);
546 manage_existing_windows(conn, &prophs, root);
548 /* Get pointer position to see on which screen we’re starting */
549 xcb_query_pointer_reply_t *reply;
550 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
551 LOG("Could not get pointer position\n");
555 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
556 if (screen == NULL) {
557 LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
560 if (screen->current_workspace != 0) {
561 LOG("Ok, I need to go to the other workspace\n");
562 c_ws = &workspaces[screen->current_workspace];
565 /* Enter xcb’s event handler */
566 xcb_event_wait_for_event_loop(&evenths);