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_wm.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 */
47 char *application_path;
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 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
56 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
58 /* The event handlers need to be global because they are accessed by our custom event handler
59 in handle_button_press(), needed for graphical resizing */
60 xcb_event_handlers_t evenths;
61 xcb_atom_t atoms[NUM_ATOMS];
66 * Do some sanity checks and then reparent the window.
69 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
70 LOG("managing window.\n");
71 xcb_drawable_t d = { window };
72 xcb_get_geometry_cookie_t geomc;
73 xcb_get_geometry_reply_t *geom;
74 xcb_get_window_attributes_reply_t *attr = 0;
76 if (wa.tag == TAG_COOKIE) {
77 /* Check if the window is mapped (it could be not mapped when intializing and
78 calling manage_window() for every window) */
79 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
82 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
86 wa.u.override_redirect = attr->override_redirect;
89 /* Don’t manage clients with the override_redirect flag */
90 if (wa.u.override_redirect)
93 /* Check if the window is already managed */
94 if (table_get(byChild, window))
97 /* Get the initial geometry (position, size, …) */
98 geomc = xcb_get_geometry(conn, d);
101 wa.u.cookie = xcb_get_window_attributes(conn, window);
102 attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
104 geom = xcb_get_geometry_reply(conn, geomc, 0);
106 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
107 geom->x, geom->y, geom->width, geom->height);
108 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
119 * reparent_window() gets called when a new window was opened and becomes a child of the root
120 * window, or it gets called by us when we manage the already existing windows at startup.
122 * Essentially, this is the point where we take over control.
125 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
126 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
127 int16_t x, int16_t y, uint16_t width, uint16_t height) {
129 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
133 /* We are interested in property changes */
134 mask = XCB_CW_EVENT_MASK;
135 values[0] = CHILD_EVENT_MASK;
136 xcb_change_window_attributes(conn, child, mask, values);
138 /* Map the window first to avoid flickering */
139 xcb_map_window(conn, child);
141 /* Place requests for properties ASAP */
142 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
143 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
144 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
146 Client *new = table_get(byChild, child);
148 /* Events for already managed windows should already be filtered in manage_window() */
151 LOG("reparenting new client\n");
152 new = calloc(sizeof(Client), 1);
153 new->force_reconfigure = true;
155 /* Update the data structures */
156 Client *old_focused = CUR_CELL->currently_focused;
158 new->container = CUR_CELL;
159 new->workspace = new->container->workspace;
161 new->frame = xcb_generate_id(conn);
163 new->rect.width = width;
164 new->rect.height = height;
168 /* Don’t generate events for our new window, it should *not* be managed */
169 mask |= XCB_CW_OVERRIDE_REDIRECT;
172 /* We want to know when… */
173 mask |= XCB_CW_EVENT_MASK;
174 values[1] = FRAME_EVENT_MASK;
176 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
178 i3Font *font = load_font(conn, config.font);
179 width = min(width, c_ws->rect.x + c_ws->rect.width);
180 height = min(height, c_ws->rect.y + c_ws->rect.height);
182 Rect framerect = {x, y,
183 width + 2 + 2, /* 2 px border at each side */
184 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
186 /* Yo dawg, I heard you like windows, so I create a window around your window… */
187 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
189 /* Put the client inside the save set. Upon termination (whether killed or normal exit
190 does not matter) of the window manager, these clients will be correctly reparented
191 to their most closest living ancestor (= cleanup) */
192 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
194 /* Generate a graphics context for the titlebar */
195 new->titlegc = xcb_generate_id(conn);
196 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
198 /* Moves the original window into the new frame we've created for it */
199 new->awaiting_useless_unmap = true;
200 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
201 if (xcb_request_check(conn, cookie) != NULL) {
202 LOG("Could not reparent the window, aborting\n");
203 xcb_destroy_window(conn, new->frame);
208 /* Put our data structure (Client) into the table */
209 table_put(byParent, new->frame, new);
210 table_put(byChild, child, new);
212 /* We need to grab the mouse buttons for click to focus */
213 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
214 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
215 1 /* left mouse button */,
216 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
218 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
220 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
221 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
222 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
223 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
224 LOG("Window is a dock.\n");
226 new->titlebar_position = TITLEBAR_OFF;
227 new->force_reconfigure = true;
228 new->container = NULL;
229 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
234 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
236 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
237 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
238 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
239 of the screen. This is because the only possibility for bars is at to be at the top/bottom
240 with maximum horizontal size.
241 TODO: bars at the top */
242 new->desired_height = strut[3];
243 if (new->desired_height == 0) {
244 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
245 new->desired_height = height;
247 LOG("the client wants to be %d pixels high\n", new->desired_height);
249 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
250 new->desired_height = height;
254 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
255 if (CUR_CELL->workspace->fullscreen_client == NULL) {
257 CUR_CELL->currently_focused = new;
258 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
261 /* If we are in fullscreen, we should lower the window to not be annoying */
262 uint32_t values[] = { XCB_STACK_MODE_BELOW };
263 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
266 /* Insert into the currently active container, if it’s not a dock window */
268 /* Insert after the old active client, if existing. If it does not exist, the
269 container is empty and it does not matter, where we insert it */
270 if (old_focused != NULL && !old_focused->dock)
271 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
272 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
274 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
277 /* Check if the window already got the fullscreen hint set */
279 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
280 (state = xcb_get_property_value(preply)) != NULL)
281 /* Check all set _NET_WM_STATEs */
282 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
283 if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
284 /* If the window got the fullscreen state, we just toggle fullscreen
285 and don’t event bother to redraw the layout – that would not change
287 toggle_fullscreen(conn, new);
295 * Go through all existing windows (if the window manager is restarted) and manage them
298 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
299 xcb_query_tree_reply_t *reply;
301 xcb_window_t *children;
302 xcb_get_window_attributes_cookie_t *cookies;
304 /* Get the tree of windows whose parent is the root window (= all) */
305 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
308 len = xcb_query_tree_children_length(reply);
309 cookies = smalloc(len * sizeof(*cookies));
311 /* Request the window attributes for every window */
312 children = xcb_query_tree_children(reply);
313 for(i = 0; i < len; ++i)
314 cookies[i] = xcb_get_window_attributes(conn, children[i]);
316 /* Call manage_window with the attributes for every window */
317 for(i = 0; i < len; ++i) {
318 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
319 manage_window(prophs, conn, children[i], wa);
326 int main(int argc, char *argv[], char *env[]) {
328 char *override_configpath = NULL;
329 xcb_connection_t *conn;
330 xcb_property_handlers_t prophs;
332 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
334 setlocale(LC_ALL, "");
336 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
337 if (!isatty(fileno(stdout)))
338 setbuf(stdout, NULL);
340 application_path = sstrdup(argv[0]);
342 while ((opt = getopt(argc, argv, "c:")) != -1) {
345 override_configpath = sstrdup(optarg);
348 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
353 /* Initialize the table data structures for each workspace */
356 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
357 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
359 byChild = alloc_table();
360 byParent = alloc_table();
362 load_configuration(override_configpath);
364 conn = xcb_connect(NULL, &screens);
366 /* Place requests for the atoms we need as soon as possible */
367 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
369 REQUEST_ATOM(_NET_SUPPORTED);
370 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
371 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
372 REQUEST_ATOM(_NET_WM_NAME);
373 REQUEST_ATOM(_NET_WM_STATE);
374 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
375 REQUEST_ATOM(_NET_WM_DESKTOP);
376 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
377 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
378 REQUEST_ATOM(WM_PROTOCOLS);
379 REQUEST_ATOM(WM_DELETE_WINDOW);
380 REQUEST_ATOM(UTF8_STRING);
382 /* TODO: this has to be more beautiful somewhen */
383 int major, minor, error;
385 major = XkbMajorVersion;
386 minor = XkbMinorVersion;
390 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
391 fprintf(stderr, "XkbOpenDisplay() failed\n");
396 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
397 fprintf(stderr, "XKB not supported by X-server\n");
400 /* end of ugliness */
402 xcb_event_handlers_init(conn, &evenths);
404 /* DEBUG: Trap all events and print them */
405 for (i = 2; i < 128; ++i)
406 xcb_event_set_handler(&evenths, i, handle_event, 0);
408 for (i = 0; i < 256; ++i)
409 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
411 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
412 xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
414 /* Key presses/releases are pretty obvious, I think */
415 xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
416 xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
418 /* Enter window = user moved his mouse over the window */
419 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
421 /* Button press = user pushed a mouse button over one of our windows */
422 xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
424 /* Map notify = there is a new window */
425 xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
427 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
428 it any longer. Usually, the client destroys the window shortly afterwards. */
429 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
431 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
432 it to set up ignore the following enter_notify events */
433 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
435 /* Configure request = window tried to change size on its own */
436 xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
438 /* Client message are sent to the root window. The only interesting client message
439 for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
440 xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
442 /* Initialize the property handlers */
443 xcb_property_handlers_init(&prophs, &evenths);
445 /* Watch size hints (to obey correct aspect ratio) */
446 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
448 /* Get the root window and set the event mask */
449 root = xcb_aux_get_screen(conn, screens)->root;
451 uint32_t mask = XCB_CW_EVENT_MASK;
452 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
453 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
454 projector), the root window gets a
456 XCB_EVENT_MASK_PROPERTY_CHANGE |
457 XCB_EVENT_MASK_ENTER_WINDOW };
458 xcb_change_window_attributes(conn, root, mask, values);
460 /* Setup NetWM atoms */
461 #define GET_ATOM(name) { \
462 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
464 printf("Could not get atom " #name "\n"); \
467 atoms[name] = reply->atom; \
471 GET_ATOM(_NET_SUPPORTED);
472 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
473 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
474 GET_ATOM(_NET_WM_NAME);
475 GET_ATOM(_NET_WM_STATE);
476 GET_ATOM(_NET_WM_WINDOW_TYPE);
477 GET_ATOM(_NET_WM_DESKTOP);
478 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
479 GET_ATOM(_NET_WM_STRUT_PARTIAL);
480 GET_ATOM(WM_PROTOCOLS);
481 GET_ATOM(WM_DELETE_WINDOW);
482 GET_ATOM(UTF8_STRING);
484 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
485 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
487 /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
488 xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
490 /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
491 xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
493 /* Set up the atoms we support */
494 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
495 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
496 /* Set up the window manager’s name */
497 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
498 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
500 /* Grab the bound keys */
502 TAILQ_FOREACH(bind, &bindings, bindings) {
503 LOG("Grabbing %d\n", bind->keycode);
504 if (bind->mods & BIND_MODE_SWITCH)
505 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
506 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
509 /* check for Xinerama */
510 LOG("Checking for Xinerama...\n");
511 initialize_xinerama(conn);
515 manage_existing_windows(conn, &prophs, root);
517 /* Get pointer position to see on which screen we’re starting */
518 xcb_query_pointer_reply_t *reply;
519 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
520 LOG("Could not get pointer position\n");
524 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
525 if (screen == NULL) {
526 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
529 if (screen->current_workspace != 0) {
530 LOG("Ok, I need to go to the other workspace\n");
531 c_ws = &workspaces[screen->current_workspace];
534 /* Enter xcb’s event handler */
535 xcb_event_wait_for_event_loop(&evenths);