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>
45 #define TERMINAL "/usr/pkg/bin/urxvt"
49 TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings);
50 SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
51 xcb_event_handlers_t evenths;
53 xcb_window_t root_win;
56 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
61 * TODO: what exactly does this, what happens if we leave stuff out?
64 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa)
66 printf("managing window.\n");
67 xcb_drawable_t d = { window };
68 xcb_get_geometry_cookie_t geomc;
69 xcb_get_geometry_reply_t *geom;
70 xcb_get_window_attributes_reply_t *attr = 0;
71 if(wa.tag == TAG_COOKIE)
73 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
76 if(attr->map_state != XCB_MAP_STATE_VIEWABLE)
78 printf("Window 0x%08x is not mapped. Ignoring.\n", window);
83 wa.u.override_redirect = attr->override_redirect;
85 if(!wa.u.override_redirect && table_get(byChild, window))
87 printf("Window 0x%08x already managed. Ignoring.\n", window);
91 if(wa.u.override_redirect)
93 printf("Window 0x%08x has override-redirect set. Ignoring.\n", window);
97 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);
107 reparent_window(c, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height);
108 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
115 * reparent_window() gets called when a new window was opened and becomes a child of the root
116 * window, or it gets called by us when we manage the already existing windows at startup.
118 * Essentially, this is the point, where we take over control.
121 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
122 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
123 int16_t x, int16_t y, uint16_t width, uint16_t height) {
125 xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
127 /* Place requests for propertys ASAP */
128 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
129 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
131 Client *new = table_get(byChild, child);
133 /* TODO: When does this happen for existing clients? Is that a bug? */
134 printf("oh, it's new\n");
135 new = calloc(sizeof(Client), 1);
136 /* We initialize x and y with the invalid coordinates -1 so that they will
137 get updated at the next render_layout() at any case */
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);
246 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
247 xcb_query_tree_cookie_t wintree;
248 xcb_query_tree_reply_t *rep;
250 xcb_window_t *children;
251 xcb_get_window_attributes_cookie_t *cookies;
253 wintree = xcb_query_tree(c, root);
254 rep = xcb_query_tree_reply(c, wintree, 0);
257 len = xcb_query_tree_children_length(rep);
258 cookies = malloc(len * sizeof(*cookies));
264 children = xcb_query_tree_children(rep);
265 for(i = 0; i < len; ++i)
266 cookies[i] = xcb_get_window_attributes(c, children[i]);
267 for(i = 0; i < len; ++i)
269 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
270 manage_window(prophs, c, children[i], wa);
275 int main(int argc, char *argv[], char *env[]) {
278 xcb_property_handlers_t prophs;
281 /* Initialize the table data structures for each workspace */
284 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
285 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
287 byChild = alloc_table();
288 byParent = alloc_table();
290 c = xcb_connect(NULL, &screens);
292 /* TODO: this has to be more beautiful somewhen */
293 int major, minor, error;
295 major = XkbMajorVersion;
296 minor = XkbMinorVersion;
300 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
301 fprintf(stderr, "XkbOpenDisplay() failed\n");
306 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
307 fprintf(stderr, "XKB not supported by X-server\n");
310 /* end of ugliness */
312 xcb_event_handlers_init(c, &evenths);
313 for(i = 2; i < 128; ++i)
314 xcb_event_set_handler(&evenths, i, handle_event, 0);
316 for(i = 0; i < 256; ++i)
317 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
319 /* Expose = an Application should redraw itself. That is, we have to redraw our
320 * contents (= top/bottom bar, titlebars for each window) */
321 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
323 /* Key presses/releases are pretty obvious, I think */
324 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
325 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
327 /* Enter window = user moved his mouse over the window */
328 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
330 /* Button press = user pushed a mouse button over one of our windows */
331 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
333 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
335 xcb_property_handlers_init(&prophs, &evenths);
336 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
338 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
340 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
342 root = xcb_aux_get_screen(c, screens)->root;
345 uint32_t mask = XCB_CW_EVENT_MASK;
346 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW};
347 xcb_change_window_attributes(c, root, mask, values);
349 /* Setup NetWM atoms */
350 /* TODO: needs cleanup, needs more xcb (asynchronous), needs more error checking */
351 #define GET_ATOM(name) { \
352 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, 0, strlen(#name), #name), NULL); \
354 printf("Could not get atom " #name "\n"); \
357 atoms[name] = reply->atom; \
361 GET_ATOM(_NET_SUPPORTED);
362 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
363 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
364 GET_ATOM(_NET_WM_NAME);
365 GET_ATOM(_NET_WM_STATE);
366 GET_ATOM(_NET_WM_WINDOW_TYPE);
367 GET_ATOM(_NET_WM_DESKTOP);
368 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
369 GET_ATOM(_NET_WM_STRUT_PARTIAL);
370 GET_ATOM(UTF8_STRING);
372 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
373 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
375 check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
377 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
379 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME] , atoms[UTF8_STRING], 8, strlen("i3"), "i3");
381 #define BIND(key, modifier, cmd) { \
382 Binding *new = malloc(sizeof(Binding)); \
383 new->keycode = key; \
384 new->mods = modifier; \
385 new->command = cmd; \
386 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
390 BIND(38, BIND_MODE_SWITCH, "foo");
392 BIND(30, 0, "exec /usr/pkg/bin/urxvt");
394 BIND(41, BIND_MOD_1, "f");
396 BIND(43, BIND_MOD_1, "s");
397 BIND(26, BIND_MOD_1, "d");
399 BIND(44, BIND_MOD_1, "h");
400 BIND(45, BIND_MOD_1, "j");
401 BIND(46, BIND_MOD_1, "k");
402 BIND(47, BIND_MOD_1, "l");
404 BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
405 BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
406 BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
407 BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
409 BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
410 BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
411 BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
412 BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
414 BIND(10, BIND_MOD_1 , "1");
415 BIND(11, BIND_MOD_1 , "2");
416 BIND(12, BIND_MOD_1 , "3");
417 BIND(13, BIND_MOD_1 , "4");
418 BIND(14, BIND_MOD_1 , "5");
419 BIND(15, BIND_MOD_1 , "6");
420 BIND(16, BIND_MOD_1 , "7");
421 BIND(17, BIND_MOD_1 , "8");
422 BIND(18, BIND_MOD_1 , "9");
423 BIND(19, BIND_MOD_1 , "0");
426 TAILQ_FOREACH(bind, &bindings, bindings) {
427 printf("Grabbing %d\n", bind->keycode);
428 if (bind->mods & BIND_MODE_SWITCH)
429 xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
430 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
433 /* check for Xinerama */
434 printf("Checking for Xinerama...\n");
435 initialize_xinerama(c);
437 start_application(TERMINAL);
441 manage_existing_windows(c, &prophs, root);
443 /* Get pointer position to see on which screen we’re starting */
444 xcb_query_pointer_cookie_t pointer_cookie = xcb_query_pointer(c, root);
445 xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c, pointer_cookie, NULL);
447 printf("Could not get pointer position\n");
451 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
452 if (screen == NULL) {
453 printf("ERROR: No such screen\n");
456 if (screen->current_workspace != 0) {
457 printf("Ok, I need to go to the other workspace\n");
458 c_ws = &workspaces[screen->current_workspace];
461 xcb_event_wait_for_event_loop(&evenths);