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