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 /* Check if the window is already managed */
91 if (!wa.u.override_redirect && table_get(byChild, window))
94 /* Don’t manage clients with the override_redirect flag */
95 if (wa.u.override_redirect)
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 /* TODO: When does this happen for existing clients? Is that a bug? */
138 printf("oh, it's new\n");
139 new = calloc(sizeof(Client), 1);
140 new->force_reconfigure = true;
145 /* Update the data structures */
146 CUR_CELL->currently_focused = new;
147 new->container = CUR_CELL;
149 new->frame = xcb_generate_id(conn);
151 new->rect.width = width;
152 new->rect.height = height;
154 /* Don’t generate events for our new window, it should *not* be managed */
155 mask |= XCB_CW_OVERRIDE_REDIRECT;
158 /* We want to know when… */
159 mask |= XCB_CW_EVENT_MASK;
160 values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */
161 XCB_EVENT_MASK_BUTTON_RELEASE |
162 XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */
163 XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */
165 printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
167 i3Font *font = load_font(conn, config.font);
168 width = min(width, c_ws->rect.x + c_ws->rect.width);
169 height = min(height, c_ws->rect.y + c_ws->rect.height);
171 Rect framerect = {x, y,
172 width + 2 + 2, /* 2 px border at each side */
173 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
175 /* Yo dawg, I heard you like windows, so I create a window around your window… */
176 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
178 /* Put the client inside the save set. Upon termination (whether killed or normal exit
179 does not matter) of the window manager, these clients will be correctly reparented
180 to their most closest living ancestor (= cleanup) */
181 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
183 /* Generate a graphics context for the titlebar */
184 new->titlegc = xcb_generate_id(conn);
185 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
187 /* Put our data structure (Client) into the table */
188 table_put(byParent, new->frame, new);
189 table_put(byChild, child, new);
191 /* Moves the original window into the new frame we've created for it */
192 new->awaiting_useless_unmap = true;
193 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
194 check_error(conn, cookie, "Could not reparent window");
196 /* We are interested in property changes */
197 mask = XCB_CW_EVENT_MASK;
198 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
199 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
200 XCB_EVENT_MASK_ENTER_WINDOW;
201 cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
202 check_error(conn, cookie, "Could not change window attributes");
204 /* We need to grab the mouse buttons for click to focus */
205 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
206 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
207 1 /* left mouse button */,
208 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
210 /* Focus the new window */
211 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
213 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
215 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
216 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
217 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
218 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
219 printf("Window is a dock.\n");
221 new->titlebar_position = TITLEBAR_OFF;
222 new->force_reconfigure = true;
223 new->container = NULL;
224 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
228 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
230 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
231 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
232 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
233 of the screen. This is because the only possibility for bars is at to be at the top/bottom
234 with maximum horizontal size.
235 TODO: bars at the top */
236 new->desired_height = strut[3];
237 printf("the client wants to be %d pixels height\n", new->desired_height);
240 /* Insert into the currently active container, if it’s not a dock window */
242 CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
248 * Go through all existing windows (if the window manager is restarted) and manage them
251 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
252 xcb_query_tree_reply_t *reply;
254 xcb_window_t *children;
255 xcb_get_window_attributes_cookie_t *cookies;
257 /* Get the tree of windows whose parent is the root window (= all) */
258 if ((reply = xcb_query_tree_reply(c, xcb_query_tree(c, root), 0)) == NULL)
261 len = xcb_query_tree_children_length(reply);
262 cookies = smalloc(len * sizeof(*cookies));
264 /* Request the window attributes for every window */
265 children = xcb_query_tree_children(reply);
266 for(i = 0; i < len; ++i)
267 cookies[i] = xcb_get_window_attributes(c, children[i]);
269 /* Call manage_window with the attributes for every window */
270 for(i = 0; i < len; ++i) {
271 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
272 manage_window(prophs, c, children[i], wa);
278 int main(int argc, char *argv[], char *env[]) {
281 xcb_property_handlers_t prophs;
283 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
285 application_path = sstrdup(argv[0]);
287 /* Initialize the table data structures for each workspace */
290 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
291 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
293 byChild = alloc_table();
294 byParent = alloc_table();
296 load_configuration("i3.config");
298 c = xcb_connect(NULL, &screens);
300 /* Place requests for the atoms we need as soon as possible */
301 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(c, 0, strlen(#name), #name);
303 REQUEST_ATOM(_NET_SUPPORTED);
304 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
305 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
306 REQUEST_ATOM(_NET_WM_NAME);
307 REQUEST_ATOM(_NET_WM_STATE);
308 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
309 REQUEST_ATOM(_NET_WM_DESKTOP);
310 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
311 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
312 REQUEST_ATOM(UTF8_STRING);
314 /* TODO: this has to be more beautiful somewhen */
315 int major, minor, error;
317 major = XkbMajorVersion;
318 minor = XkbMinorVersion;
322 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
323 fprintf(stderr, "XkbOpenDisplay() failed\n");
328 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
329 fprintf(stderr, "XKB not supported by X-server\n");
332 /* end of ugliness */
334 xcb_event_handlers_init(c, &evenths);
336 /* DEBUG: Trap all events and print them */
337 for (i = 2; i < 128; ++i)
338 xcb_event_set_handler(&evenths, i, handle_event, 0);
340 for (i = 0; i < 256; ++i)
341 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
343 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
344 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
346 /* Key presses/releases are pretty obvious, I think */
347 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
348 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
350 /* Enter window = user moved his mouse over the window */
351 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
353 /* Button press = user pushed a mouse button over one of our windows */
354 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
356 /* Map notify = there is a new window */
357 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
359 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
360 it any longer. Usually, the client destroys the window shortly afterwards. */
361 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
363 /* Client message = client changed its properties (EWMH) */
364 /* TODO: can’t we do this via property handlers? */
365 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
367 /* Initialize the property handlers */
368 xcb_property_handlers_init(&prophs, &evenths);
370 /* Watch the WM_NAME (= title of the window) property */
371 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
373 /* Get the root window and set the event mask */
374 root = xcb_aux_get_screen(c, screens)->root;
376 uint32_t mask = XCB_CW_EVENT_MASK;
377 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
378 XCB_EVENT_MASK_PROPERTY_CHANGE |
379 XCB_EVENT_MASK_ENTER_WINDOW };
380 xcb_change_window_attributes(c, root, mask, values);
382 /* Setup NetWM atoms */
383 #define GET_ATOM(name) { \
384 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, atom_cookies[name], NULL); \
386 printf("Could not get atom " #name "\n"); \
389 atoms[name] = reply->atom; \
393 GET_ATOM(_NET_SUPPORTED);
394 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
395 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
396 GET_ATOM(_NET_WM_NAME);
397 GET_ATOM(_NET_WM_STATE);
398 GET_ATOM(_NET_WM_WINDOW_TYPE);
399 GET_ATOM(_NET_WM_DESKTOP);
400 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
401 GET_ATOM(_NET_WM_STRUT_PARTIAL);
402 GET_ATOM(UTF8_STRING);
404 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
405 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
407 /* Set up the atoms we support */
408 check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
409 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
411 /* Set up the window manager’s name */
412 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
413 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
415 /* Grab the bound keys */
417 TAILQ_FOREACH(bind, &bindings, bindings) {
418 printf("Grabbing %d\n", bind->keycode);
419 if (bind->mods & BIND_MODE_SWITCH)
420 xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
421 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
424 /* check for Xinerama */
425 printf("Checking for Xinerama...\n");
426 initialize_xinerama(c);
428 /* DEBUG: Start a terminal */
429 start_application(config.terminal);
433 manage_existing_windows(c, &prophs, root);
435 /* Get pointer position to see on which screen we’re starting */
436 xcb_query_pointer_reply_t *reply;
437 if ((reply = xcb_query_pointer_reply(c, xcb_query_pointer(c, root), NULL)) == NULL) {
438 printf("Could not get pointer position\n");
442 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
443 if (screen == NULL) {
444 printf("ERROR: No such screen\n");
447 if (screen->current_workspace != 0) {
448 printf("Ok, I need to go to the other workspace\n");
449 c_ws = &workspaces[screen->current_workspace];
452 /* Enter xcb’s event handler */
453 xcb_event_wait_for_event_loop(&evenths);