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 xcb_event_handlers_t evenths;
52 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 /* Insert into the currently active container */
145 CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
147 /* Update the data structures */
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, pattern);
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_ASYNC, 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 SLIST_INSERT_HEAD(&(new->container->workspace->dock_clients), new, dock_clients);
229 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
231 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
232 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
233 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
234 of the screen. This is because the only possibility for bars is at to be at the top/bottom
235 with maximum horizontal size.
236 TODO: bars at the top */
237 new->desired_height = strut[3];
238 printf("the client wants to be %d pixels height\n", new->desired_height);
244 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
245 xcb_query_tree_cookie_t wintree;
246 xcb_query_tree_reply_t *rep;
248 xcb_window_t *children;
249 xcb_get_window_attributes_cookie_t *cookies;
251 wintree = xcb_query_tree(c, root);
252 rep = xcb_query_tree_reply(c, wintree, 0);
255 len = xcb_query_tree_children_length(rep);
256 cookies = malloc(len * sizeof(*cookies));
262 children = xcb_query_tree_children(rep);
263 for(i = 0; i < len; ++i)
264 cookies[i] = xcb_get_window_attributes(c, children[i]);
265 for(i = 0; i < len; ++i)
267 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
268 manage_window(prophs, c, children[i], wa);
273 int main(int argc, char *argv[], char *env[]) {
276 xcb_property_handlers_t prophs;
279 /* Initialize the table data structures for each workspace */
282 memset(&evenths, 0, sizeof(xcb_event_handlers_t));
283 memset(&prophs, 0, sizeof(xcb_property_handlers_t));
285 byChild = alloc_table();
286 byParent = alloc_table();
288 c = xcb_connect(NULL, &screens);
290 /* TODO: this has to be more beautiful somewhen */
291 int major, minor, error;
293 major = XkbMajorVersion;
294 minor = XkbMinorVersion;
298 if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
299 fprintf(stderr, "XkbOpenDisplay() failed\n");
304 if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
305 fprintf(stderr, "XKB not supported by X-server\n");
308 /* end of ugliness */
310 xcb_event_handlers_init(c, &evenths);
311 for(i = 2; i < 128; ++i)
312 xcb_event_set_handler(&evenths, i, handle_event, 0);
314 for(i = 0; i < 256; ++i)
315 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
317 /* Expose = an Application should redraw itself. That is, we have to redraw our
318 * contents (= top/bottom bar, titlebars for each window) */
319 xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
321 /* Key presses/releases are pretty obvious, I think */
322 xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
323 xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
325 /* Enter window = user moved his mouse over the window */
326 xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
328 /* Button press = user pushed a mouse button over one of our windows */
329 xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
331 xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
333 xcb_property_handlers_init(&prophs, &evenths);
334 xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
336 xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
338 xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
340 root = xcb_aux_get_screen(c, screens)->root;
343 uint32_t mask = XCB_CW_EVENT_MASK;
344 uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW};
345 xcb_change_window_attributes(c, root, mask, values);
347 /* Setup NetWM atoms */
348 /* TODO: needs cleanup, needs more xcb (asynchronous), needs more error checking */
349 #define GET_ATOM(name) { \
350 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, 0, strlen(#name), #name), NULL); \
352 printf("Could not get atom " #name "\n"); \
355 atoms[name] = reply->atom; \
359 GET_ATOM(_NET_SUPPORTED);
360 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
361 GET_ATOM(_NET_SUPPORTING_WM_CHECK);
362 GET_ATOM(_NET_WM_NAME);
363 GET_ATOM(_NET_WM_STATE);
364 GET_ATOM(_NET_WM_WINDOW_TYPE);
365 GET_ATOM(_NET_WM_DESKTOP);
366 GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
367 GET_ATOM(_NET_WM_STRUT_PARTIAL);
368 GET_ATOM(UTF8_STRING);
370 xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
371 /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
373 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");
375 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
377 xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME] , atoms[UTF8_STRING], 8, strlen("i3"), "i3");
379 #define BIND(key, modifier, cmd) { \
380 Binding *new = malloc(sizeof(Binding)); \
381 new->keycode = key; \
382 new->mods = modifier; \
383 new->command = cmd; \
384 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
388 BIND(38, BIND_MODE_SWITCH, "foo");
390 BIND(30, 0, "exec /usr/pkg/bin/urxvt");
392 BIND(41, BIND_MOD_1, "f");
394 BIND(44, BIND_MOD_1, "h");
395 BIND(45, BIND_MOD_1, "j");
396 BIND(46, BIND_MOD_1, "k");
397 BIND(47, BIND_MOD_1, "l");
399 BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
400 BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
401 BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
402 BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
404 BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
405 BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
406 BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
407 BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
409 BIND(10, BIND_MOD_1 , "1");
410 BIND(11, BIND_MOD_1 , "2");
411 BIND(12, BIND_MOD_1 , "3");
412 BIND(13, BIND_MOD_1 , "4");
413 BIND(14, BIND_MOD_1 , "5");
414 BIND(15, BIND_MOD_1 , "6");
415 BIND(16, BIND_MOD_1 , "7");
416 BIND(17, BIND_MOD_1 , "8");
417 BIND(18, BIND_MOD_1 , "9");
418 BIND(19, BIND_MOD_1 , "0");
421 TAILQ_FOREACH(bind, &bindings, bindings) {
422 printf("Grabbing %d\n", bind->keycode);
423 if (bind->mods & BIND_MODE_SWITCH)
424 xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
425 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
428 /* check for Xinerama */
429 printf("Checking for Xinerama...\n");
430 initialize_xinerama(c);
432 start_application(TERMINAL);
436 manage_existing_windows(c, &prophs, root);
438 /* Get pointer position to see on which screen we’re starting */
439 xcb_query_pointer_cookie_t pointer_cookie = xcb_query_pointer(c, root);
440 xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c, pointer_cookie, NULL);
442 printf("Could not get pointer position\n");
446 i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
447 if (screen == NULL) {
448 printf("ERROR: No such screen\n");
451 if (screen->current_workspace != 0) {
452 printf("Ok, I need to go to the other workspace\n");
453 c_ws = &workspaces[screen->current_workspace];
456 xcb_event_wait_for_event_loop(&evenths);