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>
46 #define TERMINAL "/usr/pkg/bin/urxvt"
48 /* This is our connection to X11 for use with XKB */
51 /* The list of key bindings */
52 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
54 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
55 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
57 /* The event handlers need to be global because they are accessed by our custom event handler
58 in handle_button_press(), needed for graphical resizing */
59 xcb_event_handlers_t evenths;
60 xcb_atom_t atoms[NUM_ATOMS];
62 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
66 * Do some sanity checks and then reparent the window.
69 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa) {
70 printf("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(c, wa.u.cookie, 0)) == NULL)
82 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
86 wa.u.override_redirect = attr->override_redirect;
89 /* Check if the window is already managed */
90 if (!wa.u.override_redirect && table_get(byChild, window))
93 /* Don’t manage clients with the override_redirect flag */
94 if (wa.u.override_redirect)
97 /* Get the initial geometry (position, size, …) */
98 geomc = xcb_get_geometry(c, d);
101 wa.u.cookie = xcb_get_window_attributes(c, window);
102 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
104 geom = xcb_get_geometry_reply(c, geomc, 0);
106 reparent_window(c, 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_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 Client *new = table_get(byChild, child);
136 /* TODO: When does this happen for existing clients? Is that a bug? */
137 printf("oh, it's new\n");
138 new = calloc(sizeof(Client), 1);
139 new->force_reconfigure = true;
144 /* Update the data structures */
145 CUR_CELL->currently_focused = new;
146 new->container = CUR_CELL;
148 new->frame = xcb_generate_id(conn);
150 new->rect.width = width;
151 new->rect.height = height;
153 /* Don’t generate events for our new window, it should *not* be managed */
154 mask |= XCB_CW_OVERRIDE_REDIRECT;
157 /* We want to know when… */
158 mask |= XCB_CW_EVENT_MASK;
159 values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */
160 XCB_EVENT_MASK_BUTTON_RELEASE |
161 XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */
162 XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */
164 printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
166 i3Font *font = load_font(conn, pattern);
167 width = min(width, c_ws->rect.x + c_ws->rect.width);
168 height = min(height, c_ws->rect.y + c_ws->rect.height);
170 Rect framerect = {x, y,
171 width + 2 + 2, /* 2 px border at each side */
172 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
174 /* Yo dawg, I heard you like windows, so I create a window around your window… */
175 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
177 /* Put the client inside the save set. Upon termination (whether killed or normal exit
178 does not matter) of the window manager, these clients will be correctly reparented
179 to their most closest living ancestor (= cleanup) */
180 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
182 /* Generate a graphics context for the titlebar */
183 new->titlegc = xcb_generate_id(conn);
184 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
186 /* Put our data structure (Client) into the table */
187 table_put(byParent, new->frame, new);
188 table_put(byChild, child, new);
190 /* Moves the original window into the new frame we've created for it */
191 new->awaiting_useless_unmap = true;
192 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
193 check_error(conn, cookie, "Could not reparent window");
195 /* We are interested in property changes */
196 mask = XCB_CW_EVENT_MASK;
197 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
198 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
199 XCB_EVENT_MASK_ENTER_WINDOW;
200 cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
201 check_error(conn, cookie, "Could not change window attributes");
203 /* We need to grab the mouse buttons for click to focus */
204 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
205 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
206 1 /* left mouse button */,
207 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
209 /* Focus the new window */
210 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
212 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
214 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
215 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
216 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
217 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
218 printf("Window is a dock.\n");
220 new->titlebar_position = TITLEBAR_OFF;
221 new->force_reconfigure = true;
222 new->container = NULL;
223 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
227 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
229 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
230 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
231 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
232 of the screen. This is because the only possibility for bars is at to be at the top/bottom
233 with maximum horizontal size.
234 TODO: bars at the top */
235 new->desired_height = strut[3];
236 printf("the client wants to be %d pixels height\n", new->desired_height);
239 /* Insert into the currently active container, if it’s not a dock window */
241 CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
247 * Go through all existing windows (if the window manager is restarted) and manage them
250 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
251 xcb_query_tree_reply_t *reply;
253 xcb_window_t *children;
254 xcb_get_window_attributes_cookie_t *cookies;
256 /* Get the tree of windows whose parent is the root window (= all) */
257 if ((reply = xcb_query_tree_reply(c, xcb_query_tree(c, root), 0)) == NULL)
260 len = xcb_query_tree_children_length(reply);
261 cookies = smalloc(len * sizeof(*cookies));
263 /* Request the window attributes for every window */
264 children = xcb_query_tree_children(reply);
265 for(i = 0; i < len; ++i)
266 cookies[i] = xcb_get_window_attributes(c, children[i]);
268 /* Call manage_window with the attributes for every window */
269 for(i = 0; i < len; ++i) {
270 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
271 manage_window(prophs, c, children[i], wa);
277 int main(int argc, char *argv[], char *env[]) {
280 xcb_property_handlers_t prophs;
282 xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
284 /* Initialize the table data structures for each workspace */
287 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
288 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
290 byChild = alloc_table();
291 byParent = alloc_table();
293 c = xcb_connect(NULL, &screens);
295 /* Place requests for the atoms we need as soon as possible */
296 #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(c, 0, strlen(#name), #name);
298 REQUEST_ATOM(_NET_SUPPORTED);
299 REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
300 REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
301 REQUEST_ATOM(_NET_WM_NAME);
302 REQUEST_ATOM(_NET_WM_STATE);
303 REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
304 REQUEST_ATOM(_NET_WM_DESKTOP);
305 REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
306 REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
307 REQUEST_ATOM(UTF8_STRING);
309 /* TODO: this has to be more beautiful somewhen */
310 int major, minor, error;
312 major = XkbMajorVersion;
313 minor = XkbMinorVersion;
317 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
318 fprintf(stderr, "XkbOpenDisplay() failed\n");
323 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
324 fprintf(stderr, "XKB not supported by X-server\n");
327 /* end of ugliness */
329 xcb_event_handlers_init(c, &evenths);
331 /* DEBUG: Trap all events and print them */
332 for (i = 2; i < 128; ++i)
333 xcb_event_set_handler(&evenths, i, handle_event, 0);
335 for (i = 0; i < 256; ++i)
336 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
338 /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
339 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
341 /* Key presses/releases are pretty obvious, I think */
342 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
343 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
345 /* Enter window = user moved his mouse over the window */
346 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
348 /* Button press = user pushed a mouse button over one of our windows */
349 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
351 /* Map notify = there is a new window */
352 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
354 /* Unmap notify = window disappeared. When sent from a client, we don’t manage
355 it any longer. Usually, the client destroys the window shortly afterwards. */
356 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
358 /* Client message = client changed its properties (EWMH) */
359 /* TODO: can’t we do this via property handlers? */
360 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
362 /* Initialize the property handlers */
363 xcb_property_handlers_init(&prophs, &evenths);
365 /* Watch the WM_NAME (= title of the window) property */
366 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
368 /* Get the root window and set the event mask */
369 root = xcb_aux_get_screen(c, screens)->root;
371 uint32_t mask = XCB_CW_EVENT_MASK;
372 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
373 XCB_EVENT_MASK_PROPERTY_CHANGE |
374 XCB_EVENT_MASK_ENTER_WINDOW };
375 xcb_change_window_attributes(c, root, mask, values);
377 /* Setup NetWM atoms */
378 #define GET_ATOM(name) { \
379 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, atom_cookies[name], NULL); \
381 printf("Could not get atom " #name "\n"); \
384 atoms[name] = reply->atom; \
388 GET_ATOM(_NET_SUPPORTED);
389 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
390 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
391 GET_ATOM(_NET_WM_NAME);
392 GET_ATOM(_NET_WM_STATE);
393 GET_ATOM(_NET_WM_WINDOW_TYPE);
394 GET_ATOM(_NET_WM_DESKTOP);
395 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
396 GET_ATOM(_NET_WM_STRUT_PARTIAL);
397 GET_ATOM(UTF8_STRING);
399 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
400 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
402 /* Set up the atoms we support */
403 check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
404 ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
406 /* Set up the window manager’s name */
407 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
408 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
410 #define BIND(key, modifier, cmd) { \
411 Binding *new = malloc(sizeof(Binding)); \
412 new->keycode = key; \
413 new->mods = modifier; \
414 new->command = cmd; \
415 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
419 BIND(38, BIND_MODE_SWITCH, "foo");
421 BIND(30, 0, "exec /usr/pkg/bin/urxvt");
423 BIND(41, BIND_MOD_1, "f");
425 BIND(43, BIND_MOD_1, "s");
426 BIND(26, BIND_MOD_1, "d");
428 BIND(44, BIND_MOD_1, "h");
429 BIND(45, BIND_MOD_1, "j");
430 BIND(46, BIND_MOD_1, "k");
431 BIND(47, BIND_MOD_1, "l");
433 BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
434 BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
435 BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
436 BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
438 BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
439 BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
440 BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
441 BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
443 BIND(10, BIND_MOD_1 , "1");
444 BIND(11, BIND_MOD_1 , "2");
445 BIND(12, BIND_MOD_1 , "3");
446 BIND(13, BIND_MOD_1 , "4");
447 BIND(14, BIND_MOD_1 , "5");
448 BIND(15, BIND_MOD_1 , "6");
449 BIND(16, BIND_MOD_1 , "7");
450 BIND(17, BIND_MOD_1 , "8");
451 BIND(18, BIND_MOD_1 , "9");
452 BIND(19, BIND_MOD_1 , "0");
454 /* Grab the bound keys */
456 TAILQ_FOREACH(bind, &bindings, bindings) {
457 printf("Grabbing %d\n", bind->keycode);
458 if (bind->mods & BIND_MODE_SWITCH)
459 xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
460 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
463 /* check for Xinerama */
464 printf("Checking for Xinerama...\n");
465 initialize_xinerama(c);
467 /* DEBUG: Start a terminal */
468 start_application(TERMINAL);
472 manage_existing_windows(c, &prophs, root);
474 /* Get pointer position to see on which screen we’re starting */
475 xcb_query_pointer_reply_t *reply;
476 if ((reply = xcb_query_pointer_reply(c, xcb_query_pointer(c, root), NULL)) == NULL) {
477 printf("Could not get pointer position\n");
481 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
482 if (screen == NULL) {
483 printf("ERROR: No such screen\n");
486 if (screen->current_workspace != 0) {
487 printf("Ok, I need to go to the other workspace\n");
488 c_ws = &workspaces[screen->current_workspace];
491 /* Enter xcb’s event handler */
492 xcb_event_wait_for_event_loop(&evenths);