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, atoms[_NET_WM_NAME]);
118 * reparent_window() gets called when a new window was opened and becomes a child of the root
119 * window, or it gets called by us when we manage the already existing windows at startup.
121 * Essentially, this is the point where we take over control.
124 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
125 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
126 int16_t x, int16_t y, uint16_t width, uint16_t height) {
128 xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
130 /* Place requests for properties ASAP */
131 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
132 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
134 /* Map the window first to avoid flickering */
135 xcb_map_window(conn, child);
137 Client *new = table_get(byChild, child);
139 /* Events for already managed windows should already be filtered in manage_window() */
142 LOG("reparenting new client\n");
143 new = calloc(sizeof(Client), 1);
144 new->force_reconfigure = true;
148 /* Update the data structures */
149 Client *old_focused = CUR_CELL->currently_focused;
151 new->container = CUR_CELL;
153 new->frame = xcb_generate_id(conn);
155 new->rect.width = width;
156 new->rect.height = height;
158 /* Don’t generate events for our new window, it should *not* be managed */
159 mask |= XCB_CW_OVERRIDE_REDIRECT;
162 /* We want to know when… */
163 mask |= XCB_CW_EVENT_MASK;
164 values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */
165 XCB_EVENT_MASK_BUTTON_RELEASE |
166 XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */
167 XCB_EVENT_MASK_ENTER_WINDOW | /* …user moves cursor inside our window */
168 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; /* …the application tries to resize itself */
170 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
172 i3Font *font = load_font(conn, config.font);
173 width = min(width, c_ws->rect.x + c_ws->rect.width);
174 height = min(height, c_ws->rect.y + c_ws->rect.height);
176 Rect framerect = {x, y,
177 width + 2 + 2, /* 2 px border at each side */
178 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
180 /* Yo dawg, I heard you like windows, so I create a window around your window… */
181 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
183 /* Put the client inside the save set. Upon termination (whether killed or normal exit
184 does not matter) of the window manager, these clients will be correctly reparented
185 to their most closest living ancestor (= cleanup) */
186 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
188 /* Generate a graphics context for the titlebar */
189 new->titlegc = xcb_generate_id(conn);
190 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
192 /* Moves the original window into the new frame we've created for it */
193 new->awaiting_useless_unmap = true;
194 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
195 if (xcb_request_check(conn, cookie) != NULL) {
196 LOG("Could not reparent the window, aborting\n");
197 xcb_destroy_window(conn, new->frame);
202 /* We are interested in property changes */
203 mask = XCB_CW_EVENT_MASK;
204 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
205 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
206 XCB_EVENT_MASK_ENTER_WINDOW;
207 cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
208 if (xcb_request_check(conn, cookie) != NULL) {
209 LOG("Could not change window attributes, aborting\n");
210 xcb_destroy_window(conn, new->frame);
215 /* Put our data structure (Client) into the table */
216 table_put(byParent, new->frame, new);
217 table_put(byChild, child, new);
219 /* We need to grab the mouse buttons for click to focus */
220 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
221 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
222 1 /* left mouse button */,
223 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
225 /* Focus the new window if we’re not in fullscreen mode */
226 if (CUR_CELL->workspace->fullscreen_client == NULL) {
227 CUR_CELL->currently_focused = new;
228 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
230 /* If we are in fullscreen, we should lower the window to not be annoying */
231 uint32_t values[] = { XCB_STACK_MODE_BELOW };
232 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
235 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
237 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
238 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
239 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
240 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
241 LOG("Window is a dock.\n");
243 new->titlebar_position = TITLEBAR_OFF;
244 new->force_reconfigure = true;
245 new->container = NULL;
246 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
250 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
252 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
253 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
254 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
255 of the screen. This is because the only possibility for bars is at to be at the top/bottom
256 with maximum horizontal size.
257 TODO: bars at the top */
258 new->desired_height = strut[3];
259 LOG("the client wants to be %d pixels height\n", new->desired_height);
262 /* Insert into the currently active container, if it’s not a dock window */
264 /* Insert after the old active client, if existing. If it does not exist, the
265 container is empty and it does not matter, where we insert it */
266 if (old_focused != NULL)
267 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
268 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
270 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
277 * Go through all existing windows (if the window manager is restarted) and manage them
280 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
281 xcb_query_tree_reply_t *reply;
283 xcb_window_t *children;
284 xcb_get_window_attributes_cookie_t *cookies;
286 /* Get the tree of windows whose parent is the root window (= all) */
287 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
290 len = xcb_query_tree_children_length(reply);
291 cookies = smalloc(len * sizeof(*cookies));
293 /* Request the window attributes for every window */
294 children = xcb_query_tree_children(reply);
295 for(i = 0; i < len; ++i)
296 cookies[i] = xcb_get_window_attributes(conn, children[i]);
298 /* Call manage_window with the attributes for every window */
299 for(i = 0; i < len; ++i) {
300 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
301 manage_window(prophs, conn, children[i], wa);
308 int main(int argc, char *argv[], char *env[]) {
310 char *override_configpath = NULL;
311 xcb_connection_t *conn;
312 xcb_property_handlers_t prophs;
314 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
316 setlocale(LC_ALL, "");
318 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
319 if (!isatty(fileno(stdout)))
320 setbuf(stdout, NULL);
322 application_path = sstrdup(argv[0]);
324 while ((opt = getopt(argc, argv, "c:")) != -1) {
327 override_configpath = sstrdup(optarg);
330 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
335 /* Initialize the table data structures for each workspace */
338 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
339 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
341 byChild = alloc_table();
342 byParent = alloc_table();
344 load_configuration(override_configpath);
346 conn = xcb_connect(NULL, &screens);
348 /* Place requests for the atoms we need as soon as possible */
349 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
351 REQUEST_ATOM(_NET_SUPPORTED);
352 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
353 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
354 REQUEST_ATOM(_NET_WM_NAME);
355 REQUEST_ATOM(_NET_WM_STATE);
356 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
357 REQUEST_ATOM(_NET_WM_DESKTOP);
358 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
359 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
360 REQUEST_ATOM(UTF8_STRING);
362 /* TODO: this has to be more beautiful somewhen */
363 int major, minor, error;
365 major = XkbMajorVersion;
366 minor = XkbMinorVersion;
370 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
371 fprintf(stderr, "XkbOpenDisplay() failed\n");
376 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
377 fprintf(stderr, "XKB not supported by X-server\n");
380 /* end of ugliness */
382 xcb_event_handlers_init(conn, &evenths);
384 /* DEBUG: Trap all events and print them */
385 for (i = 2; i < 128; ++i)
386 xcb_event_set_handler(&evenths, i, handle_event, 0);
388 for (i = 0; i < 256; ++i)
389 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
391 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
392 xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
394 /* Key presses/releases are pretty obvious, I think */
395 xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
396 xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
398 /* Enter window = user moved his mouse over the window */
399 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
401 /* Button press = user pushed a mouse button over one of our windows */
402 xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
404 /* Map notify = there is a new window */
405 xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
407 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
408 it any longer. Usually, the client destroys the window shortly afterwards. */
409 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
411 /* Destroy notify is a step further than unmap notify. We handle it to make sure
412 that windows whose unmap notify was falsely ignored will get deleted properly */
413 xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
415 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
416 it to set up ignore the following enter_notify events */
417 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
419 /* Configure request = window tried to change size on its own */
420 xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
422 /* Client message = client changed its properties (EWMH) */
423 /* TODO: can’t we do this via property handlers? */
424 xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
426 /* Initialize the property handlers */
427 xcb_property_handlers_init(&prophs, &evenths);
429 /* Watch size hints (to obey correct aspect ratio) */
430 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
432 /* Get the root window and set the event mask */
433 root = xcb_aux_get_screen(conn, screens)->root;
435 uint32_t mask = XCB_CW_EVENT_MASK;
436 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
437 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
438 projector), the root window gets a
440 XCB_EVENT_MASK_PROPERTY_CHANGE |
441 XCB_EVENT_MASK_ENTER_WINDOW };
442 xcb_change_window_attributes(conn, root, mask, values);
444 /* Setup NetWM atoms */
445 #define GET_ATOM(name) { \
446 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
448 printf("Could not get atom " #name "\n"); \
451 atoms[name] = reply->atom; \
455 GET_ATOM(_NET_SUPPORTED);
456 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
457 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
458 GET_ATOM(_NET_WM_NAME);
459 GET_ATOM(_NET_WM_STATE);
460 GET_ATOM(_NET_WM_WINDOW_TYPE);
461 GET_ATOM(_NET_WM_DESKTOP);
462 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
463 GET_ATOM(_NET_WM_STRUT_PARTIAL);
464 GET_ATOM(UTF8_STRING);
466 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
467 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
469 /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
470 xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
472 /* Set up the atoms we support */
473 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
474 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
475 /* Set up the window manager’s name */
476 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
477 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
479 /* Grab the bound keys */
481 TAILQ_FOREACH(bind, &bindings, bindings) {
482 LOG("Grabbing %d\n", bind->keycode);
483 if (bind->mods & BIND_MODE_SWITCH)
484 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
485 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
488 /* check for Xinerama */
489 LOG("Checking for Xinerama...\n");
490 initialize_xinerama(conn);
494 manage_existing_windows(conn, &prophs, root);
496 /* Get pointer position to see on which screen we’re starting */
497 xcb_query_pointer_reply_t *reply;
498 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
499 LOG("Could not get pointer position\n");
503 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
504 if (screen == NULL) {
505 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
508 if (screen->current_workspace != 0) {
509 LOG("Ok, I need to go to the other workspace\n");
510 c_ws = &workspaces[screen->current_workspace];
513 /* Enter xcb’s event handler */
514 xcb_event_wait_for_event_loop(&evenths);