]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
ca714eaddf5ee2d609a7ae09734590fc56bb3866
[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
45 #define TERMINAL "/usr/pkg/bin/urxvt"
46
47 Display *xkbdpy;
48
49 TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings);
50 xcb_event_handlers_t evenths;
51
52 xcb_window_t root_win;
53 xcb_atom_t atoms[9];
54
55
56 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
57 int num_screens = 0;
58
59 /*
60  *
61  * TODO: what exactly does this, what happens if we leave stuff out?
62  *
63  */
64 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa)
65 {
66         printf("managing window.\n");
67         xcb_drawable_t d = { window };
68         xcb_get_geometry_cookie_t geomc;
69         xcb_get_geometry_reply_t *geom;
70         xcb_get_window_attributes_reply_t *attr = 0;
71         if(wa.tag == TAG_COOKIE)
72         {
73                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
74                 if(!attr)
75                         return;
76                 if(attr->map_state != XCB_MAP_STATE_VIEWABLE)
77                 {
78                         printf("Window 0x%08x is not mapped. Ignoring.\n", window);
79                         free(attr);
80                         return;
81                 }
82                 wa.tag = TAG_VALUE;
83                 wa.u.override_redirect = attr->override_redirect;
84         }
85         if(!wa.u.override_redirect && table_get(byChild, window))
86         {
87                 printf("Window 0x%08x already managed. Ignoring.\n", window);
88                 free(attr);
89                 return;
90         }
91         if(wa.u.override_redirect)
92         {
93                 printf("Window 0x%08x has override-redirect set. Ignoring.\n", window);
94                 free(attr);
95                 return;
96         }
97         geomc = xcb_get_geometry(c, d);
98         if(!attr)
99         {
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         {
107                 reparent_window(c, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height);
108                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
109         }
110         free(attr);
111         free(geom);
112 }
113
114 /*
115  * reparent_window() gets called when a new window was opened and becomes a child of the root
116  * window, or it gets called by us when we manage the already existing windows at startup.
117  *
118  * Essentially, this is the point, where we take over control.
119  *
120  */
121 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
122                 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
123                 int16_t x, int16_t y, uint16_t width, uint16_t height) {
124
125         xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
126
127         /* Place requests for propertys ASAP */
128         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
129         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
130
131         Client *new = table_get(byChild, child);
132         if (new == NULL) {
133                 /* TODO: When does this happen for existing clients? Is that a bug? */
134                 printf("oh, it's new\n");
135                 new = calloc(sizeof(Client), 1);
136                 /* We initialize x and y with the invalid coordinates -1 so that they will
137                    get updated at the next render_layout() at any case */
138                 new->rect.x = -1;
139                 new->rect.y = -1;
140         }
141         uint32_t mask = 0;
142         uint32_t values[3];
143
144         /* Insert into the currently active container */
145         CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
146
147         /* Update the data structures */
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, pattern);
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_ASYNC, 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                                 SLIST_INSERT_HEAD(&(new->container->workspace->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         render_layout(conn);
242 }
243
244 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
245         xcb_query_tree_cookie_t wintree;
246         xcb_query_tree_reply_t *rep;
247         int i, len;
248         xcb_window_t *children;
249         xcb_get_window_attributes_cookie_t *cookies;
250
251         wintree = xcb_query_tree(c, root);
252         rep = xcb_query_tree_reply(c, wintree, 0);
253         if(!rep)
254                 return;
255         len = xcb_query_tree_children_length(rep);
256         cookies = malloc(len * sizeof(*cookies));
257         if(!cookies)
258         {
259                 free(rep);
260                 return;
261         }
262         children = xcb_query_tree_children(rep);
263         for(i = 0; i < len; ++i)
264                 cookies[i] = xcb_get_window_attributes(c, children[i]);
265         for(i = 0; i < len; ++i)
266         {
267                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
268                 manage_window(prophs, c, children[i], wa);
269         }
270         free(rep);
271 }
272
273 int main(int argc, char *argv[], char *env[]) {
274         int i, screens;
275         xcb_connection_t *c;
276         xcb_property_handlers_t prophs;
277         xcb_window_t root;
278
279         /* Initialize the table data structures for each workspace */
280         init_table();
281
282         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
283         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
284
285         byChild = alloc_table();
286         byParent = alloc_table();
287
288         c = xcb_connect(NULL, &screens);
289
290         /* TODO: this has to be more beautiful somewhen */
291         int major, minor, error;
292
293         major = XkbMajorVersion;
294         minor = XkbMinorVersion;
295
296         int evBase, errBase;
297
298         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
299                 fprintf(stderr, "XkbOpenDisplay() failed\n");
300                 return 1;
301         }
302
303         int i1;
304         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
305                 fprintf(stderr, "XKB not supported by X-server\n");
306                 return 1;
307         }
308         /* end of ugliness */
309
310         xcb_event_handlers_init(c, &evenths);
311         for(i = 2; i < 128; ++i)
312                 xcb_event_set_handler(&evenths, i, handle_event, 0);
313
314         for(i = 0; i < 256; ++i)
315                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
316
317         /* Expose = an Application should redraw itself. That is, we have to redraw our
318          * contents (= top/bottom bar, titlebars for each window) */
319         xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
320
321         /* Key presses/releases are pretty obvious, I think */
322         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
323         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
324
325         /* Enter window = user moved his mouse over the window */
326         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
327
328         /* Button press = user pushed a mouse button over one of our windows */
329         xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
330
331         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
332
333         xcb_property_handlers_init(&prophs, &evenths);
334         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
335
336         xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
337
338         xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
339
340         root = xcb_aux_get_screen(c, screens)->root;
341         root_win = root;
342
343         uint32_t mask = XCB_CW_EVENT_MASK;
344         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW};
345         xcb_change_window_attributes(c, root, mask, values);
346
347         /* Setup NetWM atoms */
348         /* TODO: needs cleanup, needs more xcb (asynchronous), needs more error checking */
349 #define GET_ATOM(name) { \
350         xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, 0, strlen(#name), #name), NULL); \
351         if (!reply) { \
352                 printf("Could not get atom " #name "\n"); \
353                 exit(-1); \
354         } \
355         atoms[name] = reply->atom; \
356         free(reply); \
357 }
358
359         GET_ATOM(_NET_SUPPORTED);
360         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
361         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
362         GET_ATOM(_NET_WM_NAME);
363         GET_ATOM(_NET_WM_STATE);
364         GET_ATOM(_NET_WM_WINDOW_TYPE);
365         GET_ATOM(_NET_WM_DESKTOP);
366         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
367         GET_ATOM(_NET_WM_STRUT_PARTIAL);
368         GET_ATOM(UTF8_STRING);
369
370         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
371         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
372
373         check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
374
375         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
376
377         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME] , atoms[UTF8_STRING], 8, strlen("i3"), "i3");
378
379         #define BIND(key, modifier, cmd) { \
380                 Binding *new = malloc(sizeof(Binding)); \
381                 new->keycode = key; \
382                 new->mods = modifier; \
383                 new->command = cmd; \
384                 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
385         }
386
387         /* 38 = 'a' */
388         BIND(38, BIND_MODE_SWITCH, "foo");
389
390         BIND(30, 0, "exec /usr/pkg/bin/urxvt");
391
392         BIND(41, BIND_MOD_1, "f");
393
394         BIND(44, BIND_MOD_1, "h");
395         BIND(45, BIND_MOD_1, "j");
396         BIND(46, BIND_MOD_1, "k");
397         BIND(47, BIND_MOD_1, "l");
398
399         BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
400         BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
401         BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
402         BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
403
404         BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
405         BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
406         BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
407         BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
408
409         BIND(10, BIND_MOD_1 , "1");
410         BIND(11, BIND_MOD_1 , "2");
411         BIND(12, BIND_MOD_1 , "3");
412         BIND(13, BIND_MOD_1 , "4");
413         BIND(14, BIND_MOD_1 , "5");
414         BIND(15, BIND_MOD_1 , "6");
415         BIND(16, BIND_MOD_1 , "7");
416         BIND(17, BIND_MOD_1 , "8");
417         BIND(18, BIND_MOD_1 , "9");
418         BIND(19, BIND_MOD_1 , "0");
419
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         start_application(TERMINAL);
433
434         xcb_flush(c);
435
436         manage_existing_windows(c, &prophs, root);
437
438         /* Get pointer position to see on which screen we’re starting */
439         xcb_query_pointer_cookie_t pointer_cookie = xcb_query_pointer(c, root);
440         xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c, pointer_cookie, NULL);
441         if (reply == 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         xcb_event_wait_for_event_loop(&evenths);
457
458         /* not reached */
459         return 0;
460 }