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 /* 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(&by_child, 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_CLASS);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
121 * reparent_window() gets called when a new window was opened and becomes a child of the root
122 * window, or it gets called by us when we manage the already existing windows at startup.
124 * Essentially, this is the point where we take over control.
127 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
128 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
129 int16_t x, int16_t y, uint16_t width, uint16_t height) {
131 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
135 /* We are interested in property changes */
136 mask = XCB_CW_EVENT_MASK;
137 values[0] = CHILD_EVENT_MASK;
138 xcb_change_window_attributes(conn, child, mask, values);
140 /* Map the window first to avoid flickering */
141 xcb_map_window(conn, child);
143 /* Place requests for properties ASAP */
144 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
145 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
146 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
148 Client *new = table_get(&by_child, child);
150 /* Events for already managed windows should already be filtered in manage_window() */
153 LOG("reparenting new client\n");
154 new = calloc(sizeof(Client), 1);
155 new->force_reconfigure = true;
157 /* Update the data structures */
158 Client *old_focused = CUR_CELL->currently_focused;
160 new->container = CUR_CELL;
161 new->workspace = new->container->workspace;
163 new->frame = xcb_generate_id(conn);
165 new->rect.width = width;
166 new->rect.height = height;
170 /* Don’t generate events for our new window, it should *not* be managed */
171 mask |= XCB_CW_OVERRIDE_REDIRECT;
174 /* We want to know when… */
175 mask |= XCB_CW_EVENT_MASK;
176 values[1] = FRAME_EVENT_MASK;
178 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
180 i3Font *font = load_font(conn, config.font);
181 width = min(width, c_ws->rect.x + c_ws->rect.width);
182 height = min(height, c_ws->rect.y + c_ws->rect.height);
184 Rect framerect = {x, y,
185 width + 2 + 2, /* 2 px border at each side */
186 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
188 /* Yo dawg, I heard you like windows, so I create a window around your window… */
189 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
191 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
192 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
194 /* Put the client inside the save set. Upon termination (whether killed or normal exit
195 does not matter) of the window manager, these clients will be correctly reparented
196 to their most closest living ancestor (= cleanup) */
197 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
199 /* Generate a graphics context for the titlebar */
200 new->titlegc = xcb_generate_id(conn);
201 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
203 /* Moves the original window into the new frame we've created for it */
204 new->awaiting_useless_unmap = true;
205 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
206 if (xcb_request_check(conn, cookie) != NULL) {
207 LOG("Could not reparent the window, aborting\n");
208 xcb_destroy_window(conn, new->frame);
213 /* Put our data structure (Client) into the table */
214 table_put(&by_parent, new->frame, new);
215 table_put(&by_child, child, new);
217 /* We need to grab the mouse buttons for click to focus */
218 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
219 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
220 1 /* left mouse button */,
221 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
223 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
225 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
226 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
227 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
228 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
229 LOG("Window is a dock.\n");
231 new->titlebar_position = TITLEBAR_OFF;
232 new->force_reconfigure = true;
233 new->container = NULL;
234 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
239 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
241 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
242 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
243 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
244 of the screen. This is because the only possibility for bars is at to be at the top/bottom
245 with maximum horizontal size.
246 TODO: bars at the top */
247 new->desired_height = strut[3];
248 if (new->desired_height == 0) {
249 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
250 new->desired_height = height;
252 LOG("the client wants to be %d pixels high\n", new->desired_height);
254 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
255 new->desired_height = height;
259 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
260 if (CUR_CELL->workspace->fullscreen_client == NULL) {
262 CUR_CELL->currently_focused = new;
263 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
266 /* If we are in fullscreen, we should lower the window to not be annoying */
267 uint32_t values[] = { XCB_STACK_MODE_BELOW };
268 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
271 /* Insert into the currently active container, if it’s not a dock window */
273 /* Insert after the old active client, if existing. If it does not exist, the
274 container is empty and it does not matter, where we insert it */
275 if (old_focused != NULL && !old_focused->dock)
276 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
277 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
279 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
282 /* Check if the window already got the fullscreen hint set */
284 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
285 (state = xcb_get_property_value(preply)) != NULL)
286 /* Check all set _NET_WM_STATEs */
287 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
288 if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
289 /* If the window got the fullscreen state, we just toggle fullscreen
290 and don’t event bother to redraw the layout – that would not change
292 toggle_fullscreen(conn, new);
300 * Go through all existing windows (if the window manager is restarted) and manage them
303 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
304 xcb_query_tree_reply_t *reply;
306 xcb_window_t *children;
307 xcb_get_window_attributes_cookie_t *cookies;
309 /* Get the tree of windows whose parent is the root window (= all) */
310 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
313 len = xcb_query_tree_children_length(reply);
314 cookies = smalloc(len * sizeof(*cookies));
316 /* Request the window attributes for every window */
317 children = xcb_query_tree_children(reply);
318 for(i = 0; i < len; ++i)
319 cookies[i] = xcb_get_window_attributes(conn, children[i]);
321 /* Call manage_window with the attributes for every window */
322 for(i = 0; i < len; ++i) {
323 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
324 manage_window(prophs, conn, children[i], wa);
331 int main(int argc, char *argv[], char *env[]) {
333 char *override_configpath = NULL;
334 xcb_connection_t *conn;
335 xcb_property_handlers_t prophs;
337 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
339 setlocale(LC_ALL, "");
341 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
342 if (!isatty(fileno(stdout)))
343 setbuf(stdout, NULL);
347 while ((opt = getopt(argc, argv, "c:v")) != -1) {
350 override_configpath = sstrdup(optarg);
353 printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
356 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
361 LOG("i3 version " I3_VERSION " starting\n");
363 /* Initialize the table data structures for each workspace */
366 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
367 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
369 load_configuration(override_configpath);
371 conn = xcb_connect(NULL, &screens);
373 /* Place requests for the atoms we need as soon as possible */
374 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
376 REQUEST_ATOM(_NET_SUPPORTED);
377 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
378 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
379 REQUEST_ATOM(_NET_WM_NAME);
380 REQUEST_ATOM(_NET_WM_STATE);
381 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
382 REQUEST_ATOM(_NET_WM_DESKTOP);
383 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
384 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
385 REQUEST_ATOM(WM_PROTOCOLS);
386 REQUEST_ATOM(WM_DELETE_WINDOW);
387 REQUEST_ATOM(UTF8_STRING);
388 REQUEST_ATOM(WM_STATE);
390 /* TODO: this has to be more beautiful somewhen */
391 int major, minor, error;
393 major = XkbMajorVersion;
394 minor = XkbMinorVersion;
398 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
399 fprintf(stderr, "XkbOpenDisplay() failed\n");
404 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
405 fprintf(stderr, "XKB not supported by X-server\n");
408 /* end of ugliness */
410 xcb_event_handlers_init(conn, &evenths);
412 /* DEBUG: Trap all events and print them */
413 for (i = 2; i < 128; ++i)
414 xcb_event_set_handler(&evenths, i, handle_event, 0);
416 for (i = 0; i < 256; ++i)
417 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
419 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
420 xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
422 /* Key presses/releases are pretty obvious, I think */
423 xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
424 xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
426 /* Enter window = user moved his mouse over the window */
427 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
429 /* Button press = user pushed a mouse button over one of our windows */
430 xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
432 /* Map notify = there is a new window */
433 xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
435 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
436 it any longer. Usually, the client destroys the window shortly afterwards. */
437 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
439 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
440 it to set up ignore the following enter_notify events */
441 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
443 /* Configure request = window tried to change size on its own */
444 xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
446 /* Client message are sent to the root window. The only interesting client message
447 for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */
448 xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
450 /* Initialize the property handlers */
451 xcb_property_handlers_init(&prophs, &evenths);
453 /* Watch size hints (to obey correct aspect ratio) */
454 xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
456 /* Get the root window and set the event mask */
457 root = xcb_aux_get_screen(conn, screens)->root;
459 uint32_t mask = XCB_CW_EVENT_MASK;
460 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
461 XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
462 projector), the root window gets a
464 XCB_EVENT_MASK_PROPERTY_CHANGE |
465 XCB_EVENT_MASK_ENTER_WINDOW };
466 xcb_change_window_attributes(conn, root, mask, values);
468 /* Setup NetWM atoms */
469 #define GET_ATOM(name) { \
470 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
472 LOG("Could not get atom " #name "\n"); \
475 atoms[name] = reply->atom; \
479 GET_ATOM(_NET_SUPPORTED);
480 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
481 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
482 GET_ATOM(_NET_WM_NAME);
483 GET_ATOM(_NET_WM_STATE);
484 GET_ATOM(_NET_WM_WINDOW_TYPE);
485 GET_ATOM(_NET_WM_DESKTOP);
486 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
487 GET_ATOM(_NET_WM_STRUT_PARTIAL);
488 GET_ATOM(WM_PROTOCOLS);
489 GET_ATOM(WM_DELETE_WINDOW);
490 GET_ATOM(UTF8_STRING);
493 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
494 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
496 /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
497 xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
499 /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */
500 xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
502 /* Watch WM_CLASS (= class of the window) */
503 xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL);
505 /* Set up the atoms we support */
506 check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
507 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
508 /* Set up the window manager’s name */
509 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
510 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
512 xcb_get_numlock_mask(conn);
514 /* Grab the bound keys */
516 TAILQ_FOREACH(bind, &bindings, bindings) {
517 LOG("Grabbing %d\n", bind->keycode);
518 if (bind->mods & BIND_MODE_SWITCH)
519 xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
521 /* Grab the key in all combinations */
522 #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \
523 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
524 GRAB_KEY(bind->mods);
525 GRAB_KEY(bind->mods | xcb_numlock_mask);
526 GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
530 /* check for Xinerama */
531 LOG("Checking for Xinerama...\n");
532 initialize_xinerama(conn);
536 manage_existing_windows(conn, &prophs, root);
538 /* Get pointer position to see on which screen we’re starting */
539 xcb_query_pointer_reply_t *reply;
540 if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
541 LOG("Could not get pointer position\n");
545 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
546 if (screen == NULL) {
547 LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
550 if (screen->current_workspace != 0) {
551 LOG("Ok, I need to go to the other workspace\n");
552 c_ws = &workspaces[screen->current_workspace];
555 /* Enter xcb’s event handler */
556 xcb_event_wait_for_event_loop(&evenths);