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