]> git.sur5r.net Git - i3/i3/blob - src/mainx.c
e49362a0bcd42334e3025346a77a2dde0db131db
[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 SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
51 xcb_event_handlers_t evenths;
52
53 xcb_window_t root_win;
54 xcb_atom_t atoms[9];
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         /* 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 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
247         xcb_query_tree_cookie_t wintree;
248         xcb_query_tree_reply_t *rep;
249         int i, len;
250         xcb_window_t *children;
251         xcb_get_window_attributes_cookie_t *cookies;
252
253         wintree = xcb_query_tree(c, root);
254         rep = xcb_query_tree_reply(c, wintree, 0);
255         if(!rep)
256                 return;
257         len = xcb_query_tree_children_length(rep);
258         cookies = malloc(len * sizeof(*cookies));
259         if(!cookies)
260         {
261                 free(rep);
262                 return;
263         }
264         children = xcb_query_tree_children(rep);
265         for(i = 0; i < len; ++i)
266                 cookies[i] = xcb_get_window_attributes(c, children[i]);
267         for(i = 0; i < len; ++i)
268         {
269                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
270                 manage_window(prophs, c, children[i], wa);
271         }
272         free(rep);
273 }
274
275 int main(int argc, char *argv[], char *env[]) {
276         int i, screens;
277         xcb_connection_t *c;
278         xcb_property_handlers_t prophs;
279         xcb_window_t root;
280
281         /* Initialize the table data structures for each workspace */
282         init_table();
283
284         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
285         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
286
287         byChild = alloc_table();
288         byParent = alloc_table();
289
290         c = xcb_connect(NULL, &screens);
291
292         /* TODO: this has to be more beautiful somewhen */
293         int major, minor, error;
294
295         major = XkbMajorVersion;
296         minor = XkbMinorVersion;
297
298         int evBase, errBase;
299
300         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
301                 fprintf(stderr, "XkbOpenDisplay() failed\n");
302                 return 1;
303         }
304
305         int i1;
306         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
307                 fprintf(stderr, "XKB not supported by X-server\n");
308                 return 1;
309         }
310         /* end of ugliness */
311
312         xcb_event_handlers_init(c, &evenths);
313         for(i = 2; i < 128; ++i)
314                 xcb_event_set_handler(&evenths, i, handle_event, 0);
315
316         for(i = 0; i < 256; ++i)
317                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0);
318
319         /* Expose = an Application should redraw itself. That is, we have to redraw our
320          * contents (= top/bottom bar, titlebars for each window) */
321         xcb_event_set_expose_handler(&evenths, handle_expose_event, 0);
322
323         /* Key presses/releases are pretty obvious, I think */
324         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
325         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
326
327         /* Enter window = user moved his mouse over the window */
328         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
329
330         /* Button press = user pushed a mouse button over one of our windows */
331         xcb_event_set_button_press_handler(&evenths, handle_button_press, 0);
332
333         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
334
335         xcb_property_handlers_init(&prophs, &evenths);
336         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
337
338         xcb_event_set_client_message_handler(&evenths, handle_client_message, 0);
339
340         xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0);
341
342         root = xcb_aux_get_screen(c, screens)->root;
343         root_win = root;
344
345         uint32_t mask = XCB_CW_EVENT_MASK;
346         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW};
347         xcb_change_window_attributes(c, root, mask, values);
348
349         /* Setup NetWM atoms */
350         /* TODO: needs cleanup, needs more xcb (asynchronous), needs more error checking */
351 #define GET_ATOM(name) { \
352         xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, 0, strlen(#name), #name), NULL); \
353         if (!reply) { \
354                 printf("Could not get atom " #name "\n"); \
355                 exit(-1); \
356         } \
357         atoms[name] = reply->atom; \
358         free(reply); \
359 }
360
361         GET_ATOM(_NET_SUPPORTED);
362         GET_ATOM(_NET_WM_STATE_FULLSCREEN);
363         GET_ATOM(_NET_SUPPORTING_WM_CHECK);
364         GET_ATOM(_NET_WM_NAME);
365         GET_ATOM(_NET_WM_STATE);
366         GET_ATOM(_NET_WM_WINDOW_TYPE);
367         GET_ATOM(_NET_WM_DESKTOP);
368         GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
369         GET_ATOM(_NET_WM_STRUT_PARTIAL);
370         GET_ATOM(UTF8_STRING);
371
372         xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
373         /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
374
375         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");
376
377         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
378
379         xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME] , atoms[UTF8_STRING], 8, strlen("i3"), "i3");
380
381         #define BIND(key, modifier, cmd) { \
382                 Binding *new = malloc(sizeof(Binding)); \
383                 new->keycode = key; \
384                 new->mods = modifier; \
385                 new->command = cmd; \
386                 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
387         }
388
389         /* 38 = 'a' */
390         BIND(38, BIND_MODE_SWITCH, "foo");
391
392         BIND(30, 0, "exec /usr/pkg/bin/urxvt");
393
394         BIND(41, BIND_MOD_1, "f");
395
396         BIND(43, BIND_MOD_1, "s");
397         BIND(26, BIND_MOD_1, "d");
398
399         BIND(44, BIND_MOD_1, "h");
400         BIND(45, BIND_MOD_1, "j");
401         BIND(46, BIND_MOD_1, "k");
402         BIND(47, BIND_MOD_1, "l");
403
404         BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
405         BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
406         BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
407         BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
408
409         BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
410         BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
411         BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
412         BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
413
414         BIND(10, BIND_MOD_1 , "1");
415         BIND(11, BIND_MOD_1 , "2");
416         BIND(12, BIND_MOD_1 , "3");
417         BIND(13, BIND_MOD_1 , "4");
418         BIND(14, BIND_MOD_1 , "5");
419         BIND(15, BIND_MOD_1 , "6");
420         BIND(16, BIND_MOD_1 , "7");
421         BIND(17, BIND_MOD_1 , "8");
422         BIND(18, BIND_MOD_1 , "9");
423         BIND(19, BIND_MOD_1 , "0");
424
425         Binding *bind;
426         TAILQ_FOREACH(bind, &bindings, bindings) {
427                 printf("Grabbing %d\n", bind->keycode);
428                 if (bind->mods & BIND_MODE_SWITCH)
429                         xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
430                 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
431         }
432
433         /* check for Xinerama */
434         printf("Checking for Xinerama...\n");
435         initialize_xinerama(c);
436
437         start_application(TERMINAL);
438
439         xcb_flush(c);
440
441         manage_existing_windows(c, &prophs, root);
442
443         /* Get pointer position to see on which screen we’re starting */
444         xcb_query_pointer_cookie_t pointer_cookie = xcb_query_pointer(c, root);
445         xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c, pointer_cookie, NULL);
446         if (reply == NULL) {
447                 printf("Could not get pointer position\n");
448                 return 1;
449         }
450
451         i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
452         if (screen == NULL) {
453                 printf("ERROR: No such screen\n");
454                 return 0;
455         }
456         if (screen->current_workspace != 0) {
457                 printf("Ok, I need to go to the other workspace\n");
458                 c_ws = &workspaces[screen->current_workspace];
459         }
460
461         xcb_event_wait_for_event_loop(&evenths);
462
463         /* not reached */
464         return 0;
465 }