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