]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
114e471fee17544697122bc095b23aaa48f08638
[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         /* Check if the window is already managed */
91         if (!wa.u.override_redirect && table_get(byChild, window))
92                 goto out;
93
94         /* Don’t manage clients with the override_redirect flag */
95         if (wa.u.override_redirect)
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         if (new == NULL) {
137                 /* TODO: When does this happen for existing clients? Is that a bug? */
138                 printf("oh, it's new\n");
139                 new = calloc(sizeof(Client), 1);
140                 new->force_reconfigure = true;
141         }
142         uint32_t mask = 0;
143         uint32_t values[3];
144
145         /* Update the data structures */
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, 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                 CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
243
244         render_layout(conn);
245 }
246
247 /*
248  * Go through all existing windows (if the window manager is restarted) and manage them
249  *
250  */
251 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
252         xcb_query_tree_reply_t *reply;
253         int i, len;
254         xcb_window_t *children;
255         xcb_get_window_attributes_cookie_t *cookies;
256
257         /* Get the tree of windows whose parent is the root window (= all) */
258         if ((reply = xcb_query_tree_reply(c, xcb_query_tree(c, root), 0)) == NULL)
259                 return;
260
261         len = xcb_query_tree_children_length(reply);
262         cookies = smalloc(len * sizeof(*cookies));
263
264         /* Request the window attributes for every window */
265         children = xcb_query_tree_children(reply);
266         for(i = 0; i < len; ++i)
267                 cookies[i] = xcb_get_window_attributes(c, children[i]);
268
269         /* Call manage_window with the attributes for every window */
270         for(i = 0; i < len; ++i) {
271                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
272                 manage_window(prophs, c, children[i], wa);
273         }
274
275         free(reply);
276 }
277
278 int main(int argc, char *argv[], char *env[]) {
279         int i, screens;
280         xcb_connection_t *c;
281         xcb_property_handlers_t prophs;
282         xcb_window_t root;
283         xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
284
285         /* Disable output buffering to make redirects in .xsession actually useful for debugging */
286         if (!isatty(fileno(stdout)))
287                 setbuf(stdout, NULL);
288
289         application_path = sstrdup(argv[0]);
290
291         /* Initialize the table data structures for each workspace */
292         init_table();
293
294         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
295         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
296
297         byChild = alloc_table();
298         byParent = alloc_table();
299
300         load_configuration("i3.config");
301
302         c = xcb_connect(NULL, &screens);
303
304         /* Place requests for the atoms we need as soon as possible */
305         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(c, 0, strlen(#name), #name);
306
307         REQUEST_ATOM(_NET_SUPPORTED);
308         REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
309         REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
310         REQUEST_ATOM(_NET_WM_NAME);
311         REQUEST_ATOM(_NET_WM_STATE);
312         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
313         REQUEST_ATOM(_NET_WM_DESKTOP);
314         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
315         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
316         REQUEST_ATOM(UTF8_STRING);
317
318         /* TODO: this has to be more beautiful somewhen */
319         int major, minor, error;
320
321         major = XkbMajorVersion;
322         minor = XkbMinorVersion;
323
324         int evBase, errBase;
325
326         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
327                 fprintf(stderr, "XkbOpenDisplay() failed\n");
328                 return 1;
329         }
330
331         int i1;
332         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
333                 fprintf(stderr, "XKB not supported by X-server\n");
334                 return 1;
335         }
336         /* end of ugliness */
337
338         xcb_event_handlers_init(c, &evenths);
339
340         /* DEBUG: Trap all events and print them */
341         for (i = 2; i < 128; ++i)
342                 xcb_event_set_handler(&evenths, i, handle_event, 0);
343
344         for (i = 0; i < 256; ++i)
345                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
346
347         /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
348         xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
349
350         /* Key presses/releases are pretty obvious, I think */
351         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
352         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
353
354         /* Enter window = user moved his mouse over the window */
355         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
356
357         /* Button press = user pushed a mouse button over one of our windows */
358         xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
359
360         /* Map notify = there is a new window */
361         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
362
363         /* Unmap notify = window disappeared. When sent from a client, we don’t manage
364            it any longer. Usually, the client destroys the window shortly afterwards. */
365         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
366
367         /* Client message = client changed its properties (EWMH) */
368         /* TODO: can’t we do this via property handlers? */
369         xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
370
371         /* Initialize the property handlers */
372         xcb_property_handlers_init(&prophs, &evenths);
373
374         /* Watch the WM_NAME (= title of the window) property */
375         xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
376
377         /* Get the root window and set the event mask */
378         root = xcb_aux_get_screen(c, screens)->root;
379
380         uint32_t mask = XCB_CW_EVENT_MASK;
381         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
382                               XCB_EVENT_MASK_PROPERTY_CHANGE |
383                               XCB_EVENT_MASK_ENTER_WINDOW };
384         xcb_change_window_attributes(c, root, mask, values);
385
386         /* Setup NetWM atoms */
387         #define GET_ATOM(name) { \
388                 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, atom_cookies[name], NULL); \
389                 if (!reply) { \
390                         printf("Could not get atom " #name "\n"); \
391                         exit(-1); \
392                 } \
393                 atoms[name] = reply->atom; \
394                 free(reply); \
395         }
396
397         GET_ATOM(_NET_SUPPORTED);
398         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
399         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
400         GET_ATOM(_NET_WM_NAME);
401         GET_ATOM(_NET_WM_STATE);
402         GET_ATOM(_NET_WM_WINDOW_TYPE);
403         GET_ATOM(_NET_WM_DESKTOP);
404         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
405         GET_ATOM(_NET_WM_STRUT_PARTIAL);
406         GET_ATOM(UTF8_STRING);
407
408         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
409         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
410
411         /* Set up the atoms we support */
412         check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
413                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
414
415         /* Set up the window manager’s name */
416         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
417         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
418
419         /* Grab the bound keys */
420         Binding *bind;
421         TAILQ_FOREACH(bind, &bindings, bindings) {
422                 printf("Grabbing %d\n", bind->keycode);
423                 if (bind->mods & BIND_MODE_SWITCH)
424                         xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
425                 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
426         }
427
428         /* check for Xinerama */
429         printf("Checking for Xinerama...\n");
430         initialize_xinerama(c);
431
432         /* DEBUG: Start a terminal */
433         start_application(config.terminal);
434
435         xcb_flush(c);
436
437         manage_existing_windows(c, &prophs, root);
438
439         /* Get pointer position to see on which screen we’re starting */
440         xcb_query_pointer_reply_t *reply;
441         if ((reply = xcb_query_pointer_reply(c, xcb_query_pointer(c, root), NULL)) == NULL) {
442                 printf("Could not get pointer position\n");
443                 return 1;
444         }
445
446         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
447         if (screen == NULL) {
448                 printf("ERROR: No such screen\n");
449                 return 0;
450         }
451         if (screen->current_workspace != 0) {
452                 printf("Ok, I need to go to the other workspace\n");
453                 c_ws = &workspaces[screen->current_workspace];
454         }
455
456         /* Enter xcb’s event handler */
457         xcb_event_wait_for_event_loop(&evenths);
458
459         /* not reached */
460         return 0;
461 }