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