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