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>
23 #include <X11/XKBlib.h>
24 #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>
47 /* This is the path to i3, copied from argv[0] when starting up */
48 char *application_path;
50 /* This is our connection to X11 for use with XKB */
53 /* The list of key bindings */
54 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
56 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
57 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
59 /* The event handlers need to be global because they are accessed by our custom event handler
60 in handle_button_press(), needed for graphical resizing */
61 xcb_event_handlers_t evenths;
62 xcb_atom_t atoms[NUM_ATOMS];
67 * Do some sanity checks and then reparent the window.
70 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa) {
71 printf("managing window.\n");
72 xcb_drawable_t d = { window };
73 xcb_get_geometry_cookie_t geomc;
74 xcb_get_geometry_reply_t *geom;
75 xcb_get_window_attributes_reply_t *attr = 0;
77 if (wa.tag == TAG_COOKIE) {
78 /* Check if the window is mapped (it could be not mapped when intializing and
79 calling manage_window() for every window) */
80 if ((attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0)) == NULL)
83 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
87 wa.u.override_redirect = attr->override_redirect;
90 /* Don’t manage clients with the override_redirect flag */
91 if (wa.u.override_redirect)
94 /* Check if the window is already managed */
95 if (table_get(byChild, window))
98 /* Get the initial geometry (position, size, …) */
99 geomc = xcb_get_geometry(c, d);
102 wa.u.cookie = xcb_get_window_attributes(c, window);
103 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
105 geom = xcb_get_geometry_reply(c, geomc, 0);
107 reparent_window(c, window, attr->visual, geom->root, geom->depth,
108 geom->x, geom->y, geom->width, geom->height);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, 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;
131 /* Place requests for properties ASAP */
132 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
133 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
135 Client *new = table_get(byChild, child);
137 /* Events for already managed windows should already be filtered in manage_window() */
140 printf("reparenting new client\n");
141 new = calloc(sizeof(Client), 1);
142 new->force_reconfigure = true;
146 /* Update the data structures */
147 Client *old_focused = CUR_CELL->currently_focused;
148 CUR_CELL->currently_focused = new;
149 new->container = CUR_CELL;
151 new->frame = xcb_generate_id(conn);
153 new->rect.width = width;
154 new->rect.height = height;
156 /* Don’t generate events for our new window, it should *not* be managed */
157 mask |= XCB_CW_OVERRIDE_REDIRECT;
160 /* We want to know when… */
161 mask |= XCB_CW_EVENT_MASK;
162 values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */
163 XCB_EVENT_MASK_BUTTON_RELEASE |
164 XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */
165 XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */
167 printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
169 i3Font *font = load_font(conn, config.font);
170 width = min(width, c_ws->rect.x + c_ws->rect.width);
171 height = min(height, c_ws->rect.y + c_ws->rect.height);
173 Rect framerect = {x, y,
174 width + 2 + 2, /* 2 px border at each side */
175 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
177 /* Yo dawg, I heard you like windows, so I create a window around your window… */
178 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
180 /* Put the client inside the save set. Upon termination (whether killed or normal exit
181 does not matter) of the window manager, these clients will be correctly reparented
182 to their most closest living ancestor (= cleanup) */
183 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
185 /* Generate a graphics context for the titlebar */
186 new->titlegc = xcb_generate_id(conn);
187 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
189 /* Put our data structure (Client) into the table */
190 table_put(byParent, new->frame, new);
191 table_put(byChild, child, new);
193 /* Moves the original window into the new frame we've created for it */
194 new->awaiting_useless_unmap = true;
195 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
196 check_error(conn, cookie, "Could not reparent window");
198 /* We are interested in property changes */
199 mask = XCB_CW_EVENT_MASK;
200 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
201 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
202 XCB_EVENT_MASK_ENTER_WINDOW;
203 cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
204 check_error(conn, cookie, "Could not change window attributes");
206 /* We need to grab the mouse buttons for click to focus */
207 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
208 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
209 1 /* left mouse button */,
210 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
212 /* Focus the new window */
213 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
215 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
217 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
218 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
219 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
220 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
221 printf("Window is a dock.\n");
223 new->titlebar_position = TITLEBAR_OFF;
224 new->force_reconfigure = true;
225 new->container = NULL;
226 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
230 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
232 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
233 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
234 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
235 of the screen. This is because the only possibility for bars is at to be at the top/bottom
236 with maximum horizontal size.
237 TODO: bars at the top */
238 new->desired_height = strut[3];
239 printf("the client wants to be %d pixels height\n", new->desired_height);
242 /* Insert into the currently active container, if it’s not a dock window */
244 /* Insert after the old active client, if existing. If it does not exist, the
245 container is empty and it does not matter, where we insert it */
246 if (old_focused != NULL)
247 CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
248 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
255 * Go through all existing windows (if the window manager is restarted) and manage them
258 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
259 xcb_query_tree_reply_t *reply;
261 xcb_window_t *children;
262 xcb_get_window_attributes_cookie_t *cookies;
264 /* Get the tree of windows whose parent is the root window (= all) */
265 if ((reply = xcb_query_tree_reply(c, xcb_query_tree(c, root), 0)) == NULL)
268 len = xcb_query_tree_children_length(reply);
269 cookies = smalloc(len * sizeof(*cookies));
271 /* Request the window attributes for every window */
272 children = xcb_query_tree_children(reply);
273 for(i = 0; i < len; ++i)
274 cookies[i] = xcb_get_window_attributes(c, children[i]);
276 /* Call manage_window with the attributes for every window */
277 for(i = 0; i < len; ++i) {
278 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
279 manage_window(prophs, c, children[i], wa);
285 int main(int argc, char *argv[], char *env[]) {
288 xcb_property_handlers_t prophs;
290 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
292 /* Disable output buffering to make redirects in .xsession actually useful for debugging */
293 if (!isatty(fileno(stdout)))
294 setbuf(stdout, NULL);
296 application_path = sstrdup(argv[0]);
298 /* Initialize the table data structures for each workspace */
301 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
302 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
304 byChild = alloc_table();
305 byParent = alloc_table();
307 load_configuration("i3.config");
309 c = xcb_connect(NULL, &screens);
311 /* Place requests for the atoms we need as soon as possible */
312 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(c, 0, strlen(#name), #name);
314 REQUEST_ATOM(_NET_SUPPORTED);
315 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
316 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
317 REQUEST_ATOM(_NET_WM_NAME);
318 REQUEST_ATOM(_NET_WM_STATE);
319 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
320 REQUEST_ATOM(_NET_WM_DESKTOP);
321 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
322 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
323 REQUEST_ATOM(UTF8_STRING);
325 /* TODO: this has to be more beautiful somewhen */
326 int major, minor, error;
328 major = XkbMajorVersion;
329 minor = XkbMinorVersion;
333 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
334 fprintf(stderr, "XkbOpenDisplay() failed\n");
339 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
340 fprintf(stderr, "XKB not supported by X-server\n");
343 /* end of ugliness */
345 xcb_event_handlers_init(c, &evenths);
347 /* DEBUG: Trap all events and print them */
348 for (i = 2; i < 128; ++i)
349 xcb_event_set_handler(&evenths, i, handle_event, 0);
351 for (i = 0; i < 256; ++i)
352 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
354 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
355 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
357 /* Key presses/releases are pretty obvious, I think */
358 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
359 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
361 /* Enter window = user moved his mouse over the window */
362 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
364 /* Button press = user pushed a mouse button over one of our windows */
365 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
367 /* Map notify = there is a new window */
368 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
370 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
371 it any longer. Usually, the client destroys the window shortly afterwards. */
372 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
374 /* Configure notify = window’s configuration (geometry, stacking, …). We only need
375 it to set up ignore the following enter_notify events */
376 xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, 0);
378 /* Client message = client changed its properties (EWMH) */
379 /* TODO: can’t we do this via property handlers? */
380 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
382 /* Initialize the property handlers */
383 xcb_property_handlers_init(&prophs, &evenths);
385 /* Watch the WM_NAME (= title of the window) property */
386 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
388 /* Get the root window and set the event mask */
389 root = xcb_aux_get_screen(c, screens)->root;
391 uint32_t mask = XCB_CW_EVENT_MASK;
392 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
393 XCB_EVENT_MASK_PROPERTY_CHANGE |
394 XCB_EVENT_MASK_ENTER_WINDOW };
395 xcb_change_window_attributes(c, root, mask, values);
397 /* Setup NetWM atoms */
398 #define GET_ATOM(name) { \
399 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, atom_cookies[name], NULL); \
401 printf("Could not get atom " #name "\n"); \
404 atoms[name] = reply->atom; \
408 GET_ATOM(_NET_SUPPORTED);
409 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
410 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
411 GET_ATOM(_NET_WM_NAME);
412 GET_ATOM(_NET_WM_STATE);
413 GET_ATOM(_NET_WM_WINDOW_TYPE);
414 GET_ATOM(_NET_WM_DESKTOP);
415 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
416 GET_ATOM(_NET_WM_STRUT_PARTIAL);
417 GET_ATOM(UTF8_STRING);
419 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
420 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
422 /* Set up the atoms we support */
423 check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
424 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
426 /* Set up the window manager’s name */
427 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
428 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
430 /* Grab the bound keys */
432 TAILQ_FOREACH(bind, &bindings, bindings) {
433 printf("Grabbing %d\n", bind->keycode);
434 if (bind->mods & BIND_MODE_SWITCH)
435 xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
436 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
439 /* check for Xinerama */
440 printf("Checking for Xinerama...\n");
441 initialize_xinerama(c);
443 /* DEBUG: Start a terminal */
444 start_application(config.terminal);
448 manage_existing_windows(c, &prophs, root);
450 /* Get pointer position to see on which screen we’re starting */
451 xcb_query_pointer_reply_t *reply;
452 if ((reply = xcb_query_pointer_reply(c, xcb_query_pointer(c, root), NULL)) == NULL) {
453 printf("Could not get pointer position\n");
457 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
458 if (screen == NULL) {
459 printf("ERROR: No such screen\n");
462 if (screen->current_workspace != 0) {
463 printf("Ok, I need to go to the other workspace\n");
464 c_ws = &workspaces[screen->current_workspace];
467 /* Enter xcb’s event handler */
468 xcb_event_wait_for_event_loop(&evenths);