]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
More documentation, cleanups, and a cache for get_colorpixel()
[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 "queue.h"
36 #include "table.h"
37 #include "font.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 #define TERMINAL "/usr/pkg/bin/urxvt"
47
48 /* This is our connection to X11 for use with XKB */
49 Display *xkbdpy;
50
51 /* The list of key bindings */
52 struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
53
54 /* This is a list of Stack_Windows, global, for easier/faster access on expose events */
55 struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
56
57 /* The event handlers need to be global because they are accessed by our custom event handler
58    in handle_button_press(), needed for graphical resizing */
59 xcb_event_handlers_t evenths;
60 xcb_atom_t atoms[NUM_ATOMS];
61
62 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
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 *c, 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(c, 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         /* Check if the window is already managed */
90         if (!wa.u.override_redirect && table_get(byChild, window))
91                 goto out;
92
93         /* Don’t manage clients with the override_redirect flag */
94         if (wa.u.override_redirect)
95                 goto out;
96
97         /* Get the initial geometry (position, size, …) */
98         geomc = xcb_get_geometry(c, d);
99         if (!attr) {
100                 wa.tag = TAG_COOKIE;
101                 wa.u.cookie = xcb_get_window_attributes(c, window);
102                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
103         }
104         geom = xcb_get_geometry_reply(c, geomc, 0);
105         if (attr && geom) {
106                 reparent_window(c, 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         if (new == NULL) {
136                 /* TODO: When does this happen for existing clients? Is that a bug? */
137                 printf("oh, it's new\n");
138                 new = calloc(sizeof(Client), 1);
139                 new->force_reconfigure = true;
140         }
141         uint32_t mask = 0;
142         uint32_t values[3];
143
144         /* Update the data structures */
145         CUR_CELL->currently_focused = new;
146         new->container = CUR_CELL;
147
148         new->frame = xcb_generate_id(conn);
149         new->child = child;
150         new->rect.width = width;
151         new->rect.height = height;
152
153         /* Don’t generate events for our new window, it should *not* be managed */
154         mask |= XCB_CW_OVERRIDE_REDIRECT;
155         values[0] = 1;
156
157         /* We want to know when… */
158         mask |= XCB_CW_EVENT_MASK;
159         values[1] =     XCB_EVENT_MASK_BUTTON_PRESS |   /* …mouse is pressed/released */
160                         XCB_EVENT_MASK_BUTTON_RELEASE |
161                         XCB_EVENT_MASK_EXPOSURE |       /* …our window needs to be redrawn */
162                         XCB_EVENT_MASK_ENTER_WINDOW;    /* …user moves cursor inside our window */
163
164         printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
165
166         i3Font *font = load_font(conn, pattern);
167         width = min(width, c_ws->rect.x + c_ws->rect.width);
168         height = min(height, c_ws->rect.y + c_ws->rect.height);
169
170         Rect framerect = {x, y,
171                           width + 2 + 2,                  /* 2 px border at each side */
172                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
173
174         /* Yo dawg, I heard you like windows, so I create a window around your window… */
175         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
176
177         /* Put the client inside the save set. Upon termination (whether killed or normal exit
178            does not matter) of the window manager, these clients will be correctly reparented
179            to their most closest living ancestor (= cleanup) */
180         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
181
182         /* Generate a graphics context for the titlebar */
183         new->titlegc = xcb_generate_id(conn);
184         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
185
186         /* Put our data structure (Client) into the table */
187         table_put(byParent, new->frame, new);
188         table_put(byChild, child, new);
189
190         /* Moves the original window into the new frame we've created for it */
191         new->awaiting_useless_unmap = true;
192         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
193         check_error(conn, cookie, "Could not reparent window");
194
195         /* We are interested in property changes */
196         mask = XCB_CW_EVENT_MASK;
197         values[0] =     XCB_EVENT_MASK_PROPERTY_CHANGE |
198                         XCB_EVENT_MASK_STRUCTURE_NOTIFY |
199                         XCB_EVENT_MASK_ENTER_WINDOW;
200         cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
201         check_error(conn, cookie, "Could not change window attributes");
202
203         /* We need to grab the mouse buttons for click to focus */
204         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
205                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
206                         1 /* left mouse button */,
207                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
208
209         /* Focus the new window */
210         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
211
212         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
213         xcb_atom_t *atom;
214         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
215         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
216                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
217                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
218                                 printf("Window is a dock.\n");
219                                 new->dock = true;
220                                 new->titlebar_position = TITLEBAR_OFF;
221                                 new->force_reconfigure = true;
222                                 new->container = NULL;
223                                 SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients);
224                         }
225         }
226
227         /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
228         uint32_t *strut;
229         preply = xcb_get_property_reply(conn, strut_cookie, NULL);
230         if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
231                 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
232                    of the screen. This is because the only possibility for bars is at to be at the top/bottom
233                    with maximum horizontal size.
234                    TODO: bars at the top */
235                 new->desired_height = strut[3];
236                 printf("the client wants to be %d pixels height\n", new->desired_height);
237         }
238
239         /* Insert into the currently active container, if it’s not a dock window */
240         if (!new->dock)
241                 CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
242
243         render_layout(conn);
244 }
245
246 /*
247  * Go through all existing windows (if the window manager is restarted) and manage them
248  *
249  */
250 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
251         xcb_query_tree_reply_t *reply;
252         int i, len;
253         xcb_window_t *children;
254         xcb_get_window_attributes_cookie_t *cookies;
255
256         /* Get the tree of windows whose parent is the root window (= all) */
257         if ((reply = xcb_query_tree_reply(c, xcb_query_tree(c, root), 0)) == NULL)
258                 return;
259
260         len = xcb_query_tree_children_length(reply);
261         cookies = smalloc(len * sizeof(*cookies));
262
263         /* Request the window attributes for every window */
264         children = xcb_query_tree_children(reply);
265         for(i = 0; i < len; ++i)
266                 cookies[i] = xcb_get_window_attributes(c, children[i]);
267
268         /* Call manage_window with the attributes for every window */
269         for(i = 0; i < len; ++i) {
270                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
271                 manage_window(prophs, c, children[i], wa);
272         }
273
274         free(reply);
275 }
276
277 int main(int argc, char *argv[], char *env[]) {
278         int i, screens;
279         xcb_connection_t *c;
280         xcb_property_handlers_t prophs;
281         xcb_window_t root;
282         xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
283
284         /* Initialize the table data structures for each workspace */
285         init_table();
286
287         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
288         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
289
290         byChild = alloc_table();
291         byParent = alloc_table();
292
293         c = xcb_connect(NULL, &screens);
294
295         /* Place requests for the atoms we need as soon as possible */
296         #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(c, 0, strlen(#name), #name);
297
298         REQUEST_ATOM(_NET_SUPPORTED);
299         REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN);
300         REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK);
301         REQUEST_ATOM(_NET_WM_NAME);
302         REQUEST_ATOM(_NET_WM_STATE);
303         REQUEST_ATOM(_NET_WM_WINDOW_TYPE);
304         REQUEST_ATOM(_NET_WM_DESKTOP);
305         REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
306         REQUEST_ATOM(_NET_WM_STRUT_PARTIAL);
307         REQUEST_ATOM(UTF8_STRING);
308
309         /* TODO: this has to be more beautiful somewhen */
310         int major, minor, error;
311
312         major = XkbMajorVersion;
313         minor = XkbMinorVersion;
314
315         int evBase, errBase;
316
317         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
318                 fprintf(stderr, "XkbOpenDisplay() failed\n");
319                 return 1;
320         }
321
322         int i1;
323         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
324                 fprintf(stderr, "XKB not supported by X-server\n");
325                 return 1;
326         }
327         /* end of ugliness */
328
329         xcb_event_handlers_init(c, &evenths);
330
331         /* DEBUG: Trap all events and print them */
332         for (i = 2; i < 128; ++i)
333                 xcb_event_set_handler(&evenths, i, handle_event, 0);
334
335         for (i = 0; i < 256; ++i)
336                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
337
338         /* Expose = an Application should redraw itself, in this case it’s our titlebars. */
339         xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
340
341         /* Key presses/releases are pretty obvious, I think */
342         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
343         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
344
345         /* Enter window = user moved his mouse over the window */
346         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
347
348         /* Button press = user pushed a mouse button over one of our windows */
349         xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
350
351         /* Map notify = there is a new window */
352         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
353
354         /* Unmap notify = window disappeared. When sent from a client, we don’t manage
355            it any longer. Usually, the client destroys the window shortly afterwards. */
356         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
357
358         /* Client message = client changed its properties (EWMH) */
359         /* TODO: can’t we do this via property handlers? */
360         xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
361
362         /* Initialize the property handlers */
363         xcb_property_handlers_init(&prophs, &evenths);
364
365         /* Watch the WM_NAME (= title of the window) property */
366         xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
367
368         /* Get the root window and set the event mask */
369         root = xcb_aux_get_screen(c, screens)->root;
370
371         uint32_t mask = XCB_CW_EVENT_MASK;
372         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
373                               XCB_EVENT_MASK_PROPERTY_CHANGE |
374                               XCB_EVENT_MASK_ENTER_WINDOW };
375         xcb_change_window_attributes(c, root, mask, values);
376
377         /* Setup NetWM atoms */
378         #define GET_ATOM(name) { \
379                 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, atom_cookies[name], NULL); \
380                 if (!reply) { \
381                         printf("Could not get atom " #name "\n"); \
382                         exit(-1); \
383                 } \
384                 atoms[name] = reply->atom; \
385                 free(reply); \
386         }
387
388         GET_ATOM(_NET_SUPPORTED);
389         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
390         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
391         GET_ATOM(_NET_WM_NAME);
392         GET_ATOM(_NET_WM_STATE);
393         GET_ATOM(_NET_WM_WINDOW_TYPE);
394         GET_ATOM(_NET_WM_DESKTOP);
395         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
396         GET_ATOM(_NET_WM_STRUT_PARTIAL);
397         GET_ATOM(UTF8_STRING);
398
399         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
400         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
401
402         /* Set up the atoms we support */
403         check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
404                        ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
405
406         /* Set up the window manager’s name */
407         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
408         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3");
409
410         #define BIND(key, modifier, cmd) { \
411                 Binding *new = malloc(sizeof(Binding)); \
412                 new->keycode = key; \
413                 new->mods = modifier; \
414                 new->command = cmd; \
415                 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
416         }
417
418         /* 38 = 'a' */
419         BIND(38, BIND_MODE_SWITCH, "foo");
420
421         BIND(30, 0, "exec /usr/pkg/bin/urxvt");
422
423         BIND(41, BIND_MOD_1, "f");
424
425         BIND(43, BIND_MOD_1, "s");
426         BIND(26, BIND_MOD_1, "d");
427
428         BIND(44, BIND_MOD_1, "h");
429         BIND(45, BIND_MOD_1, "j");
430         BIND(46, BIND_MOD_1, "k");
431         BIND(47, BIND_MOD_1, "l");
432
433         BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
434         BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
435         BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
436         BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
437
438         BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
439         BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
440         BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
441         BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
442
443         BIND(10, BIND_MOD_1 , "1");
444         BIND(11, BIND_MOD_1 , "2");
445         BIND(12, BIND_MOD_1 , "3");
446         BIND(13, BIND_MOD_1 , "4");
447         BIND(14, BIND_MOD_1 , "5");
448         BIND(15, BIND_MOD_1 , "6");
449         BIND(16, BIND_MOD_1 , "7");
450         BIND(17, BIND_MOD_1 , "8");
451         BIND(18, BIND_MOD_1 , "9");
452         BIND(19, BIND_MOD_1 , "0");
453
454         /* Grab the bound keys */
455         Binding *bind;
456         TAILQ_FOREACH(bind, &bindings, bindings) {
457                 printf("Grabbing %d\n", bind->keycode);
458                 if (bind->mods & BIND_MODE_SWITCH)
459                         xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
460                 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
461         }
462
463         /* check for Xinerama */
464         printf("Checking for Xinerama...\n");
465         initialize_xinerama(c);
466
467         /* DEBUG: Start a terminal */
468         start_application(TERMINAL);
469
470         xcb_flush(c);
471
472         manage_existing_windows(c, &prophs, root);
473
474         /* Get pointer position to see on which screen we’re starting */
475         xcb_query_pointer_reply_t *reply;
476         if ((reply = xcb_query_pointer_reply(c, xcb_query_pointer(c, root), NULL)) == NULL) {
477                 printf("Could not get pointer position\n");
478                 return 1;
479         }
480
481         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
482         if (screen == NULL) {
483                 printf("ERROR: No such screen\n");
484                 return 0;
485         }
486         if (screen->current_workspace != 0) {
487                 printf("Ok, I need to go to the other workspace\n");
488                 c_ws = &workspaces[screen->current_workspace];
489         }
490
491         /* Enter xcb’s event handler */
492         xcb_event_wait_for_event_loop(&evenths);
493
494         /* not reached */
495         return 0;
496 }