]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
4d96a8c4435194fd32cb19d94d9017c032b65a45
[i3/i3] / src / mainx.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  */
11 #include <stdio.h>
12 #include <assert.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17 #include <stdbool.h>
18 #include <assert.h>
19 #include <limits.h>
20 #include <locale.h>
21
22 #include <X11/XKBlib.h>
23 #include <X11/extensions/XKB.h>
24
25 #include <xcb/xcb.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>
33
34 #include "config.h"
35 #include "data.h"
36 #include "debug.h"
37 #include "handlers.h"
38 #include "i3.h"
39 #include "layout.h"
40 #include "queue.h"
41 #include "table.h"
42 #include "util.h"
43 #include "xcb.h"
44 #include "xinerama.h"
45
46 /* This is the path to i3, copied from argv[0] when starting up */
47 char *application_path;
48
49 /* This is our connection to X11 for use with XKB */
50 Display *xkbdpy;
51
52 /* The list of key bindings */
53 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
54
55 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
56 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
57
58 /* The event handlers need to be global because they are accessed by our custom event handler
59    in handle_button_press(), needed for graphical resizing */
60 xcb_event_handlers_t evenths;
61 xcb_atom_t atoms[NUM_ATOMS];
62
63 int num_screens = 0;
64
65 /*
66  * Do some sanity checks and then reparent the window.
67  *
68  */
69 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
70         LOG("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;
75
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(conn, wa.u.cookie, 0)) == NULL)
80                         return;
81
82                 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
83                         goto out;
84
85                 wa.tag = TAG_VALUE;
86                 wa.u.override_redirect = attr->override_redirect;
87         }
88
89         /* Don’t manage clients with the override_redirect flag */
90         if (wa.u.override_redirect)
91                 goto out;
92
93         /* Check if the window is already managed */
94         if (table_get(byChild, window))
95                 goto out;
96
97         /* Get the initial geometry (position, size, …) */
98         geomc = xcb_get_geometry(conn, d);
99         if (!attr) {
100                 wa.tag = TAG_COOKIE;
101                 wa.u.cookie = xcb_get_window_attributes(conn, window);
102                 attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
103         }
104         geom = xcb_get_geometry_reply(conn, geomc, 0);
105         if (attr && geom) {
106                 reparent_window(conn, 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, atoms[_NET_WM_NAME]);
109         }
110
111         free(geom);
112 out:
113         free(attr);
114         return;
115 }
116
117 /*
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.
120  *
121  * Essentially, this is the point where we take over control.
122  *
123  */
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) {
127
128         xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
129
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);
133
134         /* Map the window first to avoid flickering */
135         xcb_map_window(conn, child);
136
137         Client *new = table_get(byChild, child);
138
139         /* Events for already managed windows should already be filtered in manage_window() */
140         assert(new == NULL);
141
142         LOG("reparenting new client\n");
143         new = calloc(sizeof(Client), 1);
144         new->force_reconfigure = true;
145         uint32_t mask = 0;
146         uint32_t values[3];
147
148         /* Update the data structures */
149         Client *old_focused = CUR_CELL->currently_focused;
150
151         new->container = CUR_CELL;
152
153         new->frame = xcb_generate_id(conn);
154         new->child = child;
155         new->rect.width = width;
156         new->rect.height = height;
157
158         /* Don’t generate events for our new window, it should *not* be managed */
159         mask |= XCB_CW_OVERRIDE_REDIRECT;
160         values[0] = 1;
161
162         /* We want to know when… */
163         mask |= XCB_CW_EVENT_MASK;
164         values[1] =     XCB_EVENT_MASK_BUTTON_PRESS |           /* …mouse is pressed/released */
165                         XCB_EVENT_MASK_BUTTON_RELEASE |
166                         XCB_EVENT_MASK_EXPOSURE |               /* …our window needs to be redrawn */
167                         XCB_EVENT_MASK_ENTER_WINDOW |           /* …user moves cursor inside our window */
168                         XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;   /* …the application tries to resize itself */
169
170         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
171
172         i3Font *font = load_font(conn, config.font);
173         width = min(width, c_ws->rect.x + c_ws->rect.width);
174         height = min(height, c_ws->rect.y + c_ws->rect.height);
175
176         Rect framerect = {x, y,
177                           width + 2 + 2,                  /* 2 px border at each side */
178                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
179
180         /* Yo dawg, I heard you like windows, so I create a window around your window… */
181         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
182
183         /* Put the client inside the save set. Upon termination (whether killed or normal exit
184            does not matter) of the window manager, these clients will be correctly reparented
185            to their most closest living ancestor (= cleanup) */
186         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
187
188         /* Generate a graphics context for the titlebar */
189         new->titlegc = xcb_generate_id(conn);
190         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
191
192         /* Moves the original window into the new frame we've created for it */
193         new->awaiting_useless_unmap = true;
194         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
195         if (xcb_request_check(conn, cookie) != NULL) {
196                 LOG("Could not reparent the window, aborting\n");
197                 xcb_destroy_window(conn, new->frame);
198                 free(new);
199                 return;
200         }
201
202         /* We are interested in property changes */
203         mask = XCB_CW_EVENT_MASK;
204         values[0] =     XCB_EVENT_MASK_PROPERTY_CHANGE |
205                         XCB_EVENT_MASK_STRUCTURE_NOTIFY |
206                         XCB_EVENT_MASK_ENTER_WINDOW;
207         cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
208         if (xcb_request_check(conn, cookie) != NULL) {
209                 LOG("Could not change window attributes, aborting\n");
210                 xcb_destroy_window(conn, new->frame);
211                 free(new);
212                 return;
213         }
214
215         /* Put our data structure (Client) into the table */
216         table_put(byParent, new->frame, new);
217         table_put(byChild, child, new);
218
219         /* We need to grab the mouse buttons for click to focus */
220         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
221                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
222                         1 /* left mouse button */,
223                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
224
225         /* Focus the new window if we’re not in fullscreen mode */
226         if (CUR_CELL->workspace->fullscreen_client == NULL) {
227                 CUR_CELL->currently_focused = new;
228                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
229         } else {
230                 /* If we are in fullscreen, we should lower the window to not be annoying */
231                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
232                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
233         }
234
235         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
236         xcb_atom_t *atom;
237         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
238         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
239                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
240                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
241                                 LOG("Window is a dock.\n");
242                                 new->dock = true;
243                                 new->titlebar_position = TITLEBAR_OFF;
244                                 new->force_reconfigure = true;
245                                 new->container = NULL;
246                                 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
247                         }
248         }
249
250         /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
251         uint32_t *strut;
252         preply = xcb_get_property_reply(conn, strut_cookie, NULL);
253         if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
254                 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
255                    of the screen. This is because the only possibility for bars is at to be at the top/bottom
256                    with maximum horizontal size.
257                    TODO: bars at the top */
258                 new->desired_height = strut[3];
259                 LOG("the client wants to be %d pixels height\n", new->desired_height);
260         }
261
262         /* Insert into the currently active container, if it’s not a dock window */
263         if (!new->dock) {
264                 /* Insert after the old active client, if existing. If it does not exist, the
265                    container is empty and it does not matter, where we insert it */
266                 if (old_focused != NULL)
267                         CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
268                 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
269
270                 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
271         }
272
273         render_layout(conn);
274 }
275
276 /*
277  * Go through all existing windows (if the window manager is restarted) and manage them
278  *
279  */
280 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
281         xcb_query_tree_reply_t *reply;
282         int i, len;
283         xcb_window_t *children;
284         xcb_get_window_attributes_cookie_t *cookies;
285
286         /* Get the tree of windows whose parent is the root window (= all) */
287         if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
288                 return;
289
290         len = xcb_query_tree_children_length(reply);
291         cookies = smalloc(len * sizeof(*cookies));
292
293         /* Request the window attributes for every window */
294         children = xcb_query_tree_children(reply);
295         for(i = 0; i < len; ++i)
296                 cookies[i] = xcb_get_window_attributes(conn, children[i]);
297
298         /* Call manage_window with the attributes for every window */
299         for(i = 0; i < len; ++i) {
300                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
301                 manage_window(prophs, conn, children[i], wa);
302         }
303
304         free(reply);
305         free(cookies);
306 }
307
308 int main(int argc, char *argv[], char *env[]) {
309         int i, screens, opt;
310         char *override_configpath = NULL;
311         xcb_connection_t *conn;
312         xcb_property_handlers_t prophs;
313         xcb_window_t root;
314         xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
315
316         setlocale(LC_ALL, "");
317
318         /* Disable output buffering to make redirects in .xsession actually useful for debugging */
319         if (!isatty(fileno(stdout)))
320                 setbuf(stdout, NULL);
321
322         application_path = sstrdup(argv[0]);
323
324         while ((opt = getopt(argc, argv, "c:")) != -1) {
325                 switch (opt) {
326                         case 'c':
327                                 override_configpath = sstrdup(optarg);
328                                 break;
329                         default:
330                                 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
331                                 exit(EXIT_FAILURE);
332                 }
333         }
334
335         /* Initialize the table data structures for each workspace */
336         init_table();
337
338         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
339         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
340
341         byChild = alloc_table();
342         byParent = alloc_table();
343
344         load_configuration(override_configpath);
345
346         conn = xcb_connect(NULL, &screens);
347
348         /* Place requests for the atoms we need as soon as possible */
349         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
350
351         REQUEST_ATOM(_NET_SUPPORTED);
352         REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
353         REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
354         REQUEST_ATOM(_NET_WM_NAME);
355         REQUEST_ATOM(_NET_WM_STATE);
356         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
357         REQUEST_ATOM(_NET_WM_DESKTOP);
358         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
359         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
360         REQUEST_ATOM(UTF8_STRING);
361
362         /* TODO: this has to be more beautiful somewhen */
363         int major, minor, error;
364
365         major = XkbMajorVersion;
366         minor = XkbMinorVersion;
367
368         int evBase, errBase;
369
370         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
371                 fprintf(stderr, "XkbOpenDisplay() failed\n");
372                 return 1;
373         }
374
375         int i1;
376         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
377                 fprintf(stderr, "XKB not supported by X-server\n");
378                 return 1;
379         }
380         /* end of ugliness */
381
382         xcb_event_handlers_init(conn, &evenths);
383
384         /* DEBUG: Trap all events and print them */
385         for (i = 2; i < 128; ++i)
386                 xcb_event_set_handler(&evenths, i, handle_event, 0);
387
388         for (i = 0; i < 256; ++i)
389                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
390
391         /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
392         xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
393
394         /* Key presses/releases are pretty obvious, I think */
395         xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
396         xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
397
398         /* Enter window = user moved his mouse over the window */
399         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
400
401         /* Button press = user pushed a mouse button over one of our windows */
402         xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL);
403
404         /* Map notify = there is a new window */
405         xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs);
406
407         /* Unmap notify = window disappeared. When sent from a client, we don’t manage
408            it any longer. Usually, the client destroys the window shortly afterwards. */
409         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
410
411         /* Destroy notify is a step further than unmap notify. We handle it to make sure
412            that windows whose unmap notify was falsely ignored will get deleted properly */
413         xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
414
415         /* Configure notify = window’s configuration (geometry, stacking, …). We only need
416            it to set up ignore the following enter_notify events */
417         xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
418
419         /* Configure request = window tried to change size on its own */
420         xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL);
421
422         /* Client message = client changed its properties (EWMH) */
423         /* TODO: can’t we do this via property handlers? */
424         xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL);
425
426         /* Initialize the property handlers */
427         xcb_property_handlers_init(&prophs, &evenths);
428
429         /* Watch size hints (to obey correct aspect ratio) */
430         xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
431
432         /* Get the root window and set the event mask */
433         root = xcb_aux_get_screen(conn, screens)->root;
434
435         uint32_t mask = XCB_CW_EVENT_MASK;
436         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
437                               XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
438                                                                            projector), the root window gets a
439                                                                            ConfigureNotify */
440                               XCB_EVENT_MASK_PROPERTY_CHANGE |
441                               XCB_EVENT_MASK_ENTER_WINDOW };
442         xcb_change_window_attributes(conn, root, mask, values);
443
444         /* Setup NetWM atoms */
445         #define GET_ATOM(name) { \
446                 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
447                 if (!reply) { \
448                         printf("Could not get atom " #name "\n"); \
449                         exit(-1); \
450                 } \
451                 atoms[name] = reply->atom; \
452                 free(reply); \
453         }
454
455         GET_ATOM(_NET_SUPPORTED);
456         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
457         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
458         GET_ATOM(_NET_WM_NAME);
459         GET_ATOM(_NET_WM_STATE);
460         GET_ATOM(_NET_WM_WINDOW_TYPE);
461         GET_ATOM(_NET_WM_DESKTOP);
462         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
463         GET_ATOM(_NET_WM_STRUT_PARTIAL);
464         GET_ATOM(UTF8_STRING);
465
466         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
467         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
468
469         /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */
470         xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL);
471
472         /* Set up the atoms we support */
473         check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
474                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
475         /* Set up the window manager’s name */
476         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
477         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
478
479         /* Grab the bound keys */
480         Binding *bind;
481         TAILQ_FOREACH(bind, &bindings, bindings) {
482                 LOG("Grabbing %d\n", bind->keycode);
483                 if (bind->mods & BIND_MODE_SWITCH)
484                         xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
485                 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
486         }
487
488         /* check for Xinerama */
489         LOG("Checking for Xinerama...\n");
490         initialize_xinerama(conn);
491
492         xcb_flush(conn);
493
494         manage_existing_windows(conn, &prophs, root);
495
496         /* Get pointer position to see on which screen we’re starting */
497         xcb_query_pointer_reply_t *reply;
498         if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
499                 LOG("Could not get pointer position\n");
500                 return 1;
501         }
502
503         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
504         if (screen == NULL) {
505                 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
506                 return 0;
507         }
508         if (screen->current_workspace != 0) {
509                 LOG("Ok, I need to go to the other workspace\n");
510                 c_ws = &workspaces[screen->current_workspace];
511         }
512
513         /* Enter xcb’s event handler */
514         xcb_event_wait_for_event_loop(&evenths);
515
516         /* not reached */
517         return 0;
518 }