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;
132 /* We are interested in property changes */
133 mask = XCB_CW_EVENT_MASK;
134 values[0] = CHILD_EVENT_MASK;
135 xcb_change_window_attributes(conn, child, mask, values);
137 /* Map the window first to avoid flickering */
138 xcb_map_window(conn, child);
140 /* Place requests for properties ASAP */
141 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
142 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
144 Client *new = table_get(byChild, child);
146 /* Events for already managed windows should already be filtered in manage_window() */
149 LOG("reparenting new client\n");
150 new = calloc(sizeof(Client), 1);
151 new->force_reconfigure = true;
153 /* Update the data structures */
154 Client *old_focused = CUR_CELL->currently_focused;
156 new->container = CUR_CELL;
157 new->workspace = new->container->workspace;
159 new->frame = xcb_generate_id(conn);
161 new->rect.width = width;
162 new->rect.height = height;
166 /* Don’t generate events for our new window, it should *not* be managed */
167 mask |= XCB_CW_OVERRIDE_REDIRECT;
170 /* We want to know when… */
171 mask |= XCB_CW_EVENT_MASK;
172 values[1] = FRAME_EVENT_MASK;
174 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
176 i3Font *font = load_font(conn, config.font);
177 width = min(width, c_ws->rect.x + c_ws->rect.width);
178 height = min(height, c_ws->rect.y + c_ws->rect.height);
180 Rect framerect = {x, y,
181 width + 2 + 2, /* 2 px border at each side */
182 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
184 /* Yo dawg, I heard you like windows, so I create a window around your window… */
185 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
187 /* Put the client inside the save set. Upon termination (whether killed or normal exit
188 does not matter) of the window manager, these clients will be correctly reparented
189 to their most closest living ancestor (= cleanup) */
190 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
192 /* Generate a graphics context for the titlebar */
193 new->titlegc = xcb_generate_id(conn);
194 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
196 /* Moves the original window into the new frame we've created for it */
197 new->awaiting_useless_unmap = true;
198 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
199 if (xcb_request_check(conn, cookie) != NULL) {
200 LOG("Could not reparent the window, aborting\n");
201 xcb_destroy_window(conn, new->frame);
206 /* Put our data structure (Client) into the table */
207 table_put(byParent, new->frame, new);
208 table_put(byChild, child, new);
210 /* We need to grab the mouse buttons for click to focus */
211 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
212 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
213 1 /* left mouse button */,
214 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
216 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
218 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
219 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
220 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
221 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
222 LOG("Window is a dock.\n");
224 new->titlebar_position = TITLEBAR_OFF;
225 new->force_reconfigure = true;
226 new->container = NULL;
227 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
232 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
234 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
235 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
236 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
237 of the screen. This is because the only possibility for bars is at to be at the top/bottom
238 with maximum horizontal size.
239 TODO: bars at the top */
240 new->desired_height = strut[3];
241 if (new->desired_height == 0) {
242 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
243 new->desired_height = height;
245 LOG("the client wants to be %d pixels high\n", new->desired_height);
247 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
248 new->desired_height = height;
252 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
253 if (CUR_CELL->workspace->fullscreen_client == NULL) {
255 CUR_CELL->currently_focused = new;
256 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
259 /* If we are in fullscreen, we should lower the window to not be annoying */
260 uint32_t values[] = { XCB_STACK_MODE_BELOW };
261 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
264 /* Insert into the currently active container, if it’s not a dock window */
266 /* Insert after the old active client, if existing. If it does not exist, the
267 container is empty and it does not matter, where we insert it */
268 if (old_focused != NULL && !old_focused->dock)
269 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
270 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
272 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
279 * Go through all existing windows (if the window manager is restarted) and manage them
282 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
283 xcb_query_tree_reply_t *reply;
285 xcb_window_t *children;
286 xcb_get_window_attributes_cookie_t *cookies;
288 /* Get the tree of windows whose parent is the root window (= all) */
289 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
292 len = xcb_query_tree_children_length(reply);
293 cookies = smalloc(len * sizeof(*cookies));
295 /* Request the window attributes for every window */
296 children = xcb_query_tree_children(reply);
297 for(i = 0; i < len; ++i)
298 cookies[i] = xcb_get_window_attributes(conn, children[i]);
300 /* Call manage_window with the attributes for every window */
301 for(i = 0; i < len; ++i) {
302 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
303 manage_window(prophs, conn, children[i], wa);
310 int main(int argc, char *argv[], char *env[]) {
312 char *override_configpath = NULL;
313 xcb_connection_t *conn;
314 xcb_property_handlers_t prophs;
316 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
318 setlocale(LC_ALL, "");
320 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
321 if (!isatty(fileno(stdout)))
322 setbuf(stdout, NULL);
324 application_path = sstrdup(argv[0]);
326 while ((opt = getopt(argc, argv, "c:")) != -1) {
329 override_configpath = sstrdup(optarg);
332 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
337 /* Initialize the table data structures for each workspace */
340 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
341 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
343 byChild = alloc_table();
344 byParent = alloc_table();
346 load_configuration(override_configpath);
348 conn = xcb_connect(NULL, &screens);
350 /* Place requests for the atoms we need as soon as possible */
351 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
353 REQUEST_ATOM(_NET_SUPPORTED);
354 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
355 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
356 REQUEST_ATOM(_NET_WM_NAME);
357 REQUEST_ATOM(_NET_WM_STATE);
358 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
359 REQUEST_ATOM(_NET_WM_DESKTOP);
360 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
361 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
362 REQUEST_ATOM(UTF8_STRING);
364 /* TODO: this has to be more beautiful somewhen */
365 int major, minor, error;
367 major = XkbMajorVersion;
368 minor = XkbMinorVersion;
372 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
373 fprintf(stderr, "XkbOpenDisplay() failed\n");
378 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
379 fprintf(stderr, "XKB not supported by X-server\n");
382 /* end of ugliness */
384 xcb_event_handlers_init(conn, &evenths);
386 /* DEBUG: Trap all events and print them */
387 for (i = 2; i < 128; ++i)
388 xcb_event_set_handler(&evenths, i, handle_event, 0);
390 for (i = 0; i < 256; ++i)
391 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
393 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
394 xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
396 /* Key presses/releases are pretty obvious, I think */
397 xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
398 xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
400 /* Enter window = user moved his mouse over the window */
401 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
403 /* Button press = user pushed a mouse button over one of our windows */
404 xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
406 /* Map notify = there is a new window */
407 xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
409 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
410 it any longer. Usually, the client destroys the window shortly afterwards. */
411 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
413 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
414 it to set up ignore the following enter_notify events */
415 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
417 /* Configure request = window tried to change size on its own */
418 xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
420 /* Client message = client changed its properties (EWMH) */
421 /* TODO: can’t we do this via property handlers? */
422 xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
424 /* Initialize the property handlers */
425 xcb_property_handlers_init(&prophs, &evenths);
427 /* Watch size hints (to obey correct aspect ratio) */
428 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
430 /* Get the root window and set the event mask */
431 root = xcb_aux_get_screen(conn, screens)->root;
433 uint32_t mask = XCB_CW_EVENT_MASK;
434 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
435 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
436 projector), the root window gets a
438 XCB_EVENT_MASK_PROPERTY_CHANGE |
439 XCB_EVENT_MASK_ENTER_WINDOW };
440 xcb_change_window_attributes(conn, root, mask, values);
442 /* Setup NetWM atoms */
443 #define GET_ATOM(name) { \
444 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
446 printf("Could not get atom " #name "\n"); \
449 atoms[name] = reply->atom; \
453 GET_ATOM(_NET_SUPPORTED);
454 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
455 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
456 GET_ATOM(_NET_WM_NAME);
457 GET_ATOM(_NET_WM_STATE);
458 GET_ATOM(_NET_WM_WINDOW_TYPE);
459 GET_ATOM(_NET_WM_DESKTOP);
460 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
461 GET_ATOM(_NET_WM_STRUT_PARTIAL);
462 GET_ATOM(UTF8_STRING);
464 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
465 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
467 /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
468 xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
470 /* Set up the atoms we support */
471 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
472 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
473 /* Set up the window manager’s name */
474 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
475 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
477 /* Grab the bound keys */
479 TAILQ_FOREACH(bind, &bindings, bindings) {
480 LOG("Grabbing %d\n", bind->keycode);
481 if (bind->mods & BIND_MODE_SWITCH)
482 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
483 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
486 /* check for Xinerama */
487 LOG("Checking for Xinerama...\n");
488 initialize_xinerama(conn);
492 manage_existing_windows(conn, &prophs, root);
494 /* Get pointer position to see on which screen we’re starting */
495 xcb_query_pointer_reply_t *reply;
496 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
497 LOG("Could not get pointer position\n");
501 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
502 if (screen == NULL) {
503 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
506 if (screen->current_workspace != 0) {
507 LOG("Ok, I need to go to the other workspace\n");
508 c_ws = &workspaces[screen->current_workspace];
511 /* Enter xcb’s event handler */
512 xcb_event_wait_for_event_loop(&evenths);