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