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