]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
Don’t start a terminal on startup
[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         /* Put our data structure (Client) into the table */
211         table_put(byParent, new->frame, new);
212         table_put(byChild, child, new);
213
214         /* We need to grab the mouse buttons for click to focus */
215         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
216                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
217                         1 /* left mouse button */,
218                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
219
220         /* Focus the new window if we’re not in fullscreen mode */
221         if (CUR_CELL->workspace->fullscreen_client == NULL) {
222                 CUR_CELL->currently_focused = new;
223                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
224         } else {
225                 /* If we are in fullscreen, we should lower the window to not be annoying */
226                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
227                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
228         }
229
230         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
231         xcb_atom_t *atom;
232         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
233         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
234                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
235                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
236                                 LOG("Window is a dock.\n");
237                                 new->dock = true;
238                                 new->titlebar_position = TITLEBAR_OFF;
239                                 new->force_reconfigure = true;
240                                 new->container = NULL;
241                                 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
242                         }
243         }
244
245         /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
246         uint32_t *strut;
247         preply = xcb_get_property_reply(conn, strut_cookie, NULL);
248         if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
249                 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
250                    of the screen. This is because the only possibility for bars is at to be at the top/bottom
251                    with maximum horizontal size.
252                    TODO: bars at the top */
253                 new->desired_height = strut[3];
254                 LOG("the client wants to be %d pixels height\n", new->desired_height);
255         }
256
257         /* Insert into the currently active container, if it’s not a dock window */
258         if (!new->dock) {
259                 /* Insert after the old active client, if existing. If it does not exist, the
260                    container is empty and it does not matter, where we insert it */
261                 if (old_focused != NULL)
262                         CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
263                 else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
264
265                 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
266         }
267
268         render_layout(conn);
269 }
270
271 /*
272  * Go through all existing windows (if the window manager is restarted) and manage them
273  *
274  */
275 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
276         xcb_query_tree_reply_t *reply;
277         int i, len;
278         xcb_window_t *children;
279         xcb_get_window_attributes_cookie_t *cookies;
280
281         /* Get the tree of windows whose parent is the root window (= all) */
282         if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
283                 return;
284
285         len = xcb_query_tree_children_length(reply);
286         cookies = smalloc(len * sizeof(*cookies));
287
288         /* Request the window attributes for every window */
289         children = xcb_query_tree_children(reply);
290         for(i = 0; i < len; ++i)
291                 cookies[i] = xcb_get_window_attributes(conn, children[i]);
292
293         /* Call manage_window with the attributes for every window */
294         for(i = 0; i < len; ++i) {
295                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
296                 manage_window(prophs, conn, children[i], wa);
297         }
298
299         free(reply);
300         free(cookies);
301 }
302
303 int main(int argc, char *argv[], char *env[]) {
304         int i, screens, opt;
305         char *override_configpath = NULL;
306         xcb_connection_t *conn;
307         xcb_property_handlers_t prophs;
308         xcb_window_t root;
309         xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
310
311         /* Disable output buffering to make redirects in .xsession actually useful for debugging */
312         if (!isatty(fileno(stdout)))
313                 setbuf(stdout, NULL);
314
315         application_path = sstrdup(argv[0]);
316
317         while ((opt = getopt(argc, argv, "c:")) != -1) {
318                 switch (opt) {
319                         case 'c':
320                                 override_configpath = sstrdup(optarg);
321                                 break;
322                         default:
323                                 fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
324                                 exit(EXIT_FAILURE);
325                 }
326         }
327
328         /* Initialize the table data structures for each workspace */
329         init_table();
330
331         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
332         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
333
334         byChild = alloc_table();
335         byParent = alloc_table();
336
337         load_configuration(override_configpath);
338
339         conn = xcb_connect(NULL, &screens);
340
341         /* Place requests for the atoms we need as soon as possible */
342         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
343
344         REQUEST_ATOM(_NET_SUPPORTED);
345         REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
346         REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
347         REQUEST_ATOM(_NET_WM_NAME);
348         REQUEST_ATOM(_NET_WM_STATE);
349         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
350         REQUEST_ATOM(_NET_WM_DESKTOP);
351         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
352         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
353         REQUEST_ATOM(UTF8_STRING);
354
355         /* TODO: this has to be more beautiful somewhen */
356         int major, minor, error;
357
358         major = XkbMajorVersion;
359         minor = XkbMinorVersion;
360
361         int evBase, errBase;
362
363         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
364                 fprintf(stderr, "XkbOpenDisplay() failed\n");
365                 return 1;
366         }
367
368         int i1;
369         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
370                 fprintf(stderr, "XKB not supported by X-server\n");
371                 return 1;
372         }
373         /* end of ugliness */
374
375         xcb_event_handlers_init(conn, &evenths);
376
377         /* DEBUG: Trap all events and print them */
378         for (i = 2; i < 128; ++i)
379                 xcb_event_set_handler(&evenths, i, handle_event, 0);
380
381         for (i = 0; i < 256; ++i)
382                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
383
384         /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
385         xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
386
387         /* Key presses/releases are pretty obvious, I think */
388         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
389         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
390
391         /* Enter window = user moved his mouse over the window */
392         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
393
394         /* Button press = user pushed a mouse button over one of our windows */
395         xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
396
397         /* Map notify = there is a new window */
398         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
399
400         /* Unmap notify = window disappeared. When sent from a client, we don’t manage
401            it any longer. Usually, the client destroys the window shortly afterwards. */
402         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
403
404         /* Configure notify = window’s configuration (geometry, stacking, …). We only need
405            it to set up ignore the following enter_notify events */
406         xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, 0);
407
408         /* Client message = client changed its properties (EWMH) */
409         /* TODO: can’t we do this via property handlers? */
410         xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
411
412         /* Initialize the property handlers */
413         xcb_property_handlers_init(&prophs, &evenths);
414
415         /* Watch the WM_NAME (= title of the window) property */
416         xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
417
418         /* Watch size hints (to obey correct aspect ratio) */
419         xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
420
421         /* Get the root window and set the event mask */
422         root = xcb_aux_get_screen(conn, screens)->root;
423
424         uint32_t mask = XCB_CW_EVENT_MASK;
425         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
426                               XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
427                                                                            projector), the root window gets a
428                                                                            ConfigureNotify */
429                               XCB_EVENT_MASK_PROPERTY_CHANGE |
430                               XCB_EVENT_MASK_ENTER_WINDOW };
431         xcb_change_window_attributes(conn, root, mask, values);
432
433         /* Setup NetWM atoms */
434         #define GET_ATOM(name) { \
435                 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
436                 if (!reply) { \
437                         printf("Could not get atom " #name "\n"); \
438                         exit(-1); \
439                 } \
440                 atoms[name] = reply->atom; \
441                 free(reply); \
442         }
443
444         GET_ATOM(_NET_SUPPORTED);
445         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
446         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
447         GET_ATOM(_NET_WM_NAME);
448         GET_ATOM(_NET_WM_STATE);
449         GET_ATOM(_NET_WM_WINDOW_TYPE);
450         GET_ATOM(_NET_WM_DESKTOP);
451         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
452         GET_ATOM(_NET_WM_STRUT_PARTIAL);
453         GET_ATOM(UTF8_STRING);
454
455         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
456         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
457
458         /* Set up the atoms we support */
459         check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
460                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
461         /* Set up the window manager’s name */
462         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
463         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
464
465         /* Grab the bound keys */
466         Binding *bind;
467         TAILQ_FOREACH(bind, &bindings, bindings) {
468                 LOG("Grabbing %d\n", bind->keycode);
469                 if (bind->mods & BIND_MODE_SWITCH)
470                         xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
471                 else xcb_grab_key(conn, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
472         }
473
474         /* check for Xinerama */
475         LOG("Checking for Xinerama...\n");
476         initialize_xinerama(conn);
477
478         xcb_flush(conn);
479
480         manage_existing_windows(conn, &prophs, root);
481
482         /* Get pointer position to see on which screen we’re starting */
483         xcb_query_pointer_reply_t *reply;
484         if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
485                 LOG("Could not get pointer position\n");
486                 return 1;
487         }
488
489         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
490         if (screen == NULL) {
491                 printf("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
492                 return 0;
493         }
494         if (screen->current_workspace != 0) {
495                 LOG("Ok, I need to go to the other workspace\n");
496                 c_ws = &workspaces[screen->current_workspace];
497         }
498
499         /* Enter xcb’s event handler */
500         xcb_event_wait_for_event_loop(&evenths);
501
502         /* not reached */
503         return 0;
504 }