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