]> git.sur5r.net Git - i3/i3/blob - mainx.c
dfe1abdf41dcd7d5f41b107aa36e4e965eb24a38
[i3/i3] / mainx.c
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <assert.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 #include <stdbool.h>
9 #include <assert.h>
10
11 #include <xcb/xcb.h>
12
13 #include <X11/XKBlib.h>
14 #include <X11/extensions/XKB.h>
15
16 #include "xcb_wm.h"
17 #include "xcb_aux.h"
18 #include "xcb_event.h"
19 #include "xcb_property.h"
20 #include "xcb_keysyms.h"
21 #include "data.h"
22
23 #include "queue.h"
24 #include "table.h"
25 #include "font.h"
26
27 #define TERMINAL "/usr/pkg/bin/urxvt"
28
29 i3Font *myfont;
30 Display *xkbdpy;
31
32 static const int TOP = 20;
33 static const int LEFT = 5;
34 static const int BOTTOM = 5;
35 static const int RIGHT = 5;
36
37 /* This is the filtered environment which will be passed to opened applications.
38  * It contains DISPLAY (naturally) and locales stuff (LC_*, LANG) */
39 static char **environment;
40
41 /* hm, xcb_wm wants us to implement this. */
42 table_t *byChild = 0;
43 table_t *byParent = 0;
44 xcb_window_t root_win;
45
46 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
47
48
49 int current_col = 0;
50 int current_row = 0;
51
52
53 int globalc = 0;
54
55
56 static const char *labelError[] = {
57     "Success",
58     "BadRequest",
59     "BadValue",
60     "BadWindow",
61     "BadPixmap",
62     "BadAtom",
63     "BadCursor",
64     "BadFont",
65     "BadMatch",
66     "BadDrawable",
67     "BadAccess",
68     "BadAlloc",
69     "BadColor",
70     "BadGC",
71     "BadIDChoice",
72     "BadName",
73     "BadLength",
74     "BadImplementation",
75 };
76
77 static const char *labelRequest[] = {
78     "no request",
79     "CreateWindow",
80     "ChangeWindowAttributes",
81     "GetWindowAttributes",
82     "DestroyWindow",
83     "DestroySubwindows",
84     "ChangeSaveSet",
85     "ReparentWindow",
86     "MapWindow",
87     "MapSubwindows",
88     "UnmapWindow",
89     "UnmapSubwindows",
90     "ConfigureWindow",
91     "CirculateWindow",
92     "GetGeometry",
93     "QueryTree",
94     "InternAtom",
95     "GetAtomName",
96     "ChangeProperty",
97     "DeleteProperty",
98     "GetProperty",
99     "ListProperties",
100     "SetSelectionOwner",
101     "GetSelectionOwner",
102     "ConvertSelection",
103     "SendEvent",
104     "GrabPointer",
105     "UngrabPointer",
106     "GrabButton",
107     "UngrabButton",
108     "ChangeActivePointerGrab",
109     "GrabKeyboard",
110     "UngrabKeyboard",
111     "GrabKey",
112     "UngrabKey",
113     "AllowEvents",
114     "GrabServer",
115     "UngrabServer",
116     "QueryPointer",
117     "GetMotionEvents",
118     "TranslateCoords",
119     "WarpPointer",
120     "SetInputFocus",
121     "GetInputFocus",
122     "QueryKeymap",
123     "OpenFont",
124     "CloseFont",
125     "QueryFont",
126     "QueryTextExtents",
127     "ListFonts",
128     "ListFontsWithInfo",
129     "SetFontPath",
130     "GetFontPath",
131     "CreatePixmap",
132     "FreePixmap",
133     "CreateGC",
134     "ChangeGC",
135     "CopyGC",
136     "SetDashes",
137     "SetClipRectangles",
138     "FreeGC",
139     "ClearArea",
140     "CopyArea",
141     "CopyPlane",
142     "PolyPoint",
143     "PolyLine",
144     "PolySegment",
145     "PolyRectangle",
146     "PolyArc",
147     "FillPoly",
148     "PolyFillRectangle",
149     "PolyFillArc",
150     "PutImage",
151     "GetImage",
152     "PolyText",
153     "PolyText",
154     "ImageText",
155     "ImageText",
156     "CreateColormap",
157     "FreeColormap",
158     "CopyColormapAndFree",
159     "InstallColormap",
160     "UninstallColormap",
161     "ListInstalledColormaps",
162     "AllocColor",
163     "AllocNamedColor",
164     "AllocColorCells",
165     "AllocColorPlanes",
166     "FreeColors",
167     "StoreColors",
168     "StoreNamedColor",
169     "QueryColors",
170     "LookupColor",
171     "CreateCursor",
172     "CreateGlyphCursor",
173     "FreeCursor",
174     "RecolorCursor",
175     "QueryBestSize",
176     "QueryExtension",
177     "ListExtensions",
178     "ChangeKeyboardMapping",
179     "GetKeyboardMapping",
180     "ChangeKeyboardControl",
181     "GetKeyboardControl",
182     "Bell",
183     "ChangePointerControl",
184     "GetPointerControl",
185     "SetScreenSaver",
186     "GetScreenSaver",
187     "ChangeHosts",
188     "ListHosts",
189     "SetAccessControl",
190     "SetCloseDownMode",
191     "KillClient",
192     "RotateProperties",
193     "ForceScreenSaver",
194     "SetPointerMapping",
195     "GetPointerMapping",
196     "SetModifierMapping",
197     "GetModifierMapping",
198     "major 120",
199     "major 121",
200     "major 122",
201     "major 123",
202     "major 124",
203     "major 125",
204     "major 126",
205     "NoOperation",
206 };
207
208 static const char *labelEvent[] = {
209     "error",
210     "reply",
211     "KeyPress",
212     "KeyRelease",
213     "ButtonPress",
214     "ButtonRelease",
215     "MotionNotify",
216     "EnterNotify",
217     "LeaveNotify",
218     "FocusIn",
219     "FocusOut",
220     "KeymapNotify",
221     "Expose",
222     "GraphicsExpose",
223     "NoExpose",
224     "VisibilityNotify",
225     "CreateNotify",
226     "DestroyNotify",
227     "UnmapNotify",
228     "MapNotify",
229     "MapRequest",
230     "ReparentNotify",
231     "ConfigureNotify",
232     "ConfigureRequest",
233     "GravityNotify",
234     "ResizeRequest",
235     "CirculateNotify",
236     "CirculateRequest",
237     "PropertyNotify",
238     "SelectionClear",
239     "SelectionRequest",
240     "SelectionNotify",
241     "ColormapNotify",
242     "ClientMessage",
243     "MappingNotify",
244 };
245
246 static const char *labelSendEvent[] = {
247     "",
248     " (from SendEvent)",
249 };
250
251 /*
252  *
253  * TODO: what exactly does this, what happens if we leave stuff out?
254  *
255  */
256 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa)
257 {
258         printf("managing window.\n");
259         xcb_drawable_t d = { window };
260         xcb_get_geometry_cookie_t geomc;
261         xcb_get_geometry_reply_t *geom;
262         xcb_get_window_attributes_reply_t *attr = 0;
263         if(wa.tag == TAG_COOKIE)
264         {
265                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
266                 if(!attr)
267                         return;
268                 if(attr->map_state != XCB_MAP_STATE_VIEWABLE)
269                 {
270                         printf("Window 0x%08x is not mapped. Ignoring.\n", window);
271                         free(attr);
272                         return;
273                 }
274                 wa.tag = TAG_VALUE;
275                 wa.u.override_redirect = attr->override_redirect;
276         }
277         if(!wa.u.override_redirect && table_get(byChild, window))
278         {
279                 printf("Window 0x%08x already managed. Ignoring.\n", window);
280                 free(attr);
281                 return;
282         }
283         if(wa.u.override_redirect)
284         {
285                 printf("Window 0x%08x has override-redirect set. Ignoring.\n", window);
286                 free(attr);
287                 return;
288         }
289         geomc = xcb_get_geometry(c, d);
290         if(!attr)
291         {
292                 wa.tag = TAG_COOKIE;
293                 wa.u.cookie = xcb_get_window_attributes(c, window);
294                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
295         }
296         geom = xcb_get_geometry_reply(c, geomc, 0);
297         if(attr && geom)
298         {
299                 reparent_window(c, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height);
300                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
301         }
302         free(attr);
303         free(geom);
304 }
305
306
307 /*
308  * Returns the colorpixel to use for the given hex color (think of HTML).
309  *
310  * The hex_color has to start with #, for example #FF00FF.
311  *
312  * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
313  * This has to be done by the caller.
314  *
315  */
316 uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex) {
317         #define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
318         char strgroups[3][3] = {{hex[1], hex[2], '\0'},
319                                 {hex[3], hex[4], '\0'},
320                                 {hex[5], hex[6], '\0'}};
321         int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
322                         RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
323                         RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
324
325         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
326
327         xcb_colormap_t colormapId = xcb_generate_id(conn);
328         xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, colormapId, window, root_screen->root_visual);
329         xcb_alloc_color_reply_t *reply = xcb_alloc_color_reply(conn,
330                         xcb_alloc_color(conn, colormapId, rgb16[0], rgb16[1], rgb16[2]), NULL);
331
332         if (!reply) {
333                 printf("color fail\n");
334                 exit(1);
335         }
336
337         uint32_t pixel = reply->pixel;
338         free(reply);
339         xcb_free_colormap(conn, colormapId);
340         return pixel;
341 }
342
343 /*
344  * (Re-)draws window decorations for a given Client
345  *
346  */
347 void decorate_window(xcb_connection_t *conn, Client *client) {
348         uint32_t mask = 0;
349         uint32_t values[3];
350         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
351         i3Font *font = load_font(conn, pattern);
352         uint32_t background_color,
353                  text_color,
354                  border_color;
355
356         if (client->container->currently_focused == client) {
357                 background_color = get_colorpixel(conn, client->frame, "#285577");
358                 text_color = get_colorpixel(conn, client->frame, "#ffffff");
359                 border_color = get_colorpixel(conn, client->frame, "#4c7899");
360         } else {
361                 background_color = get_colorpixel(conn, client->frame, "#222222");
362                 text_color = get_colorpixel(conn, client->frame, "#888888");
363                 border_color = get_colorpixel(conn, client->frame, "#333333");
364         }
365
366         /* Our plan is the following:
367            - Draw a rect around the whole client in background_color
368            - Draw two lines in a lighter color
369            - Draw the window’s title
370
371            Note that xcb_image_text apparently adds 1xp border around the font? Can anyone confirm this?
372          */
373
374         /* Draw a green rectangle around the window */
375         mask = XCB_GC_FOREGROUND;
376         values[0] = background_color;
377         xcb_change_gc(conn, client->titlegc, mask, values);
378
379         xcb_rectangle_t rect = {0, 0, client->width, client->height};
380         xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &rect);
381
382         /* Draw the lines */
383         /* TODO: this needs to be more beautiful somewhen. maybe stdarg + change_gc(gc, ...) ? */
384 #define DRAW_LINE(colorpixel, x, y, to_x, to_y) { \
385                 uint32_t draw_values[1]; \
386                 draw_values[0] = colorpixel; \
387                 xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND, draw_values); \
388                 xcb_point_t points[] = {{x, y}, {to_x, to_y}}; \
389                 xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 2, points); \
390         }
391
392         DRAW_LINE(border_color, 2, 0, client->width, 0);
393         DRAW_LINE(border_color, 2, font->height + 3, 2 + client->width, font->height + 3);
394
395         /* Draw the font */
396         mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
397
398         values[0] = text_color;
399         values[1] = background_color;
400         values[2] = font->id;
401
402         xcb_change_gc(conn, client->titlegc, mask, values);
403
404         /* TODO: utf8? */
405         char *label;
406         asprintf(&label, "gots win %08x", client->frame);
407         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), client->frame,
408                                         client->titlegc, 3 /* X */, font->height /* Y = baseline of font */, label);
409         free(label);
410 }
411
412 void render_container(xcb_connection_t *connection, Container *container) {
413         Client *client;
414         uint32_t values[4];
415         uint32_t mask = XCB_CONFIG_WINDOW_X |
416                         XCB_CONFIG_WINDOW_Y |
417                         XCB_CONFIG_WINDOW_WIDTH |
418                         XCB_CONFIG_WINDOW_HEIGHT;
419         i3Font *font = load_font(connection, pattern);
420
421         if (container->mode == MODE_DEFAULT) {
422                 int num_clients = 0;
423                 CIRCLEQ_FOREACH(client, &(container->clients), clients)
424                         num_clients++;
425                 printf("got %d clients in this default container.\n", num_clients);
426
427                 int current_client = 0;
428                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
429                         /* TODO: at the moment, every column/row is 200px. This
430                          * needs to be changed to "percentage of the screen" by
431                          * default and adjustable by the user if necessary.
432                          */
433                         values[0] = container->col * container->width; /* x */
434                         values[1] = container->row * container->height +
435                                 (container->height / num_clients) * current_client; /* y */
436                         /* TODO: vertical default layout */
437                         values[2] = container->width; /* width */
438                         values[3] = container->height / num_clients; /* height */
439                         printf("frame will be at %dx%d with size %dx%d\n",
440                                         values[0], values[1], values[2], values[3]);
441
442                         client->width = values[2];
443                         client->height = values[3];
444
445                         /* TODO: update only if necessary */
446                         xcb_configure_window(connection, client->frame, mask, values);
447
448                         /* The coordinates of the child are relative to its frame, we
449                          * add a border of 2 pixel to each value */
450                         values[0] = 2;
451                         values[1] = font->height + 2 + 2;
452                         values[2] -= values[0] + 2;
453                         values[3] -= values[1] + 2;
454                         printf("child itself will be at %dx%d with size %dx%d\n",
455                                         values[0], values[1], values[2], values[3]);
456
457                         xcb_configure_window(connection, client->child, mask, values);
458
459                         decorate_window(connection, client);
460                         current_client++;
461                 }
462         } else {
463                 /* TODO: Implement stacking */
464         }
465 }
466
467 void render_layout(xcb_connection_t *conn) {
468         int cols, rows;
469         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
470         int width = root_screen->width_in_pixels;
471         int height = root_screen->height_in_pixels;
472
473         int num_cols = table_dims.x, num_rows = table_dims.y;
474
475         printf("got %d rows and %d cols\n", num_rows, num_cols);
476         printf("each of them therefore is %d px width and %d px height\n",
477                         width / num_cols, height / num_rows);
478
479         /* Go through the whole table and render what’s necessary */
480         for (cols = 0; cols < table_dims.x; cols++)
481                 for (rows = 0; rows < table_dims.y; rows++)
482                         if (table[cols][rows] != NULL) {
483                                 Container *con = table[cols][rows];
484                                 printf("container has %d colspan, %d rowspan\n",
485                                                 con->colspan, con->rowspan);
486                                 /* Update position of the container */
487                                 con->row = rows;
488                                 con->col = cols;
489                                 con->width = (width / num_cols) * con->colspan;
490                                 con->height = (height / num_rows) * con->rowspan;
491
492                                 /* Render it */
493                                 render_container(conn, table[cols][rows]);
494                         }
495
496         xcb_flush(conn);
497 }
498
499 /*
500  * Let’s own this window…
501  *
502  */
503 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
504                 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
505                 int16_t x, int16_t y, uint16_t width, uint16_t height) {
506
507         Client *new = table_get(byChild, child);
508         if (new == NULL) {
509                 printf("oh, it's new\n");
510                 new = calloc(sizeof(Client), 1);
511         }
512         uint32_t mask = 0;
513         uint32_t values[3];
514         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
515
516         /* Insert into the currently active container */
517         CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
518
519         printf("currently_focused = %p\n", new);
520         CUR_CELL->currently_focused = new;
521         new->container = CUR_CELL;
522
523         new->frame = xcb_generate_id(conn);
524         new->child = child;
525         new->width = width;
526         new->height = height;
527
528         /* TODO: what do these mean? */
529         mask |= XCB_CW_BACK_PIXEL;
530         values[0] = root_screen->white_pixel;
531
532         mask |= XCB_CW_OVERRIDE_REDIRECT;
533         values[1] = 1;
534
535         mask |= XCB_CW_EVENT_MASK;
536         values[2] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE
537                 | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_ENTER_WINDOW;
538
539         printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
540
541         /* Yo dawg, I heard you like windows, so I create a window around your window… */
542         xcb_create_window(conn,
543                         depth,
544                         new->frame,
545                         root,
546                         x,
547                         y,
548                         width + LEFT + RIGHT,
549                         height + TOP + BOTTOM,
550                         /* border_width */ 0,
551                         XCB_WINDOW_CLASS_INPUT_OUTPUT,
552                         visual,
553                         mask,
554                         values);
555         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
556
557         /* Map the window on the screen (= make it visible) */
558         xcb_map_window(conn, new->frame);
559
560         /* Generate a graphics context for the titlebar */
561         new->titlegc = xcb_generate_id(conn);
562         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
563
564         /* Draw decorations */
565         decorate_window(conn, new);
566
567         /* Put our data structure (Client) into the table */
568         table_put(byParent, new->frame, new);
569         table_put(byChild, child, new);
570
571         /* Moves the original window into the new frame we've created for it */
572         i3Font *font = load_font(conn, pattern);
573         xcb_reparent_window(conn, child, new->frame, 0, font->height);
574
575         /* We are interested in property changes */
576         mask = XCB_CW_EVENT_MASK;
577         values[0] =     XCB_EVENT_MASK_PROPERTY_CHANGE |
578                         XCB_EVENT_MASK_STRUCTURE_NOTIFY |
579                         XCB_EVENT_MASK_ENTER_WINDOW;
580         xcb_change_window_attributes(conn, child, mask, values);
581
582         /* TODO: At the moment, new windows just get focus */
583         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_NONE, new->frame, XCB_CURRENT_TIME);
584
585         render_layout(conn);
586
587         xcb_flush(conn);
588 }
589
590 static bool focus_window_in_container(xcb_connection_t *connection, Container *container,
591                 direction_t direction) {
592         /* If this container is empty, we’re done */
593         if (container->currently_focused == NULL)
594                 return false;
595
596         Client *candidad;
597         if (direction == D_UP)
598                 candidad = CIRCLEQ_PREV(container->currently_focused, clients);
599         else if (direction == D_DOWN)
600                 candidad = CIRCLEQ_NEXT(container->currently_focused, clients);
601
602         /* If we don’t have anything to select, we’re done */
603         if (candidad == CIRCLEQ_END(&(container->clients)))
604                 return false;
605
606         /* Set focus if we could successfully move */
607         container->currently_focused = candidad;
608         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE, candidad->child, XCB_CURRENT_TIME);
609         render_layout(connection);
610         xcb_flush(connection);
611
612         return true;
613 }
614
615 static void focus_window(xcb_connection_t *connection, direction_t direction) {
616         printf("focusing direction %d\n", direction);
617         /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
618         if (direction == D_UP || direction == D_DOWN) {
619                 /* Let’s see if we can perform up/down focus in the current container */
620                 Container *container = CUR_CELL;
621
622                 /* There always is a container. If not, current_col or current_row is wrong */
623                 assert(container != NULL);
624
625                 if (focus_window_in_container(connection, container, direction))
626                         return;
627         } else if (direction == D_LEFT || direction == D_RIGHT) {
628                 if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
629                         current_col++;
630                 else if (direction == D_LEFT && cell_exists(current_col-1, current_row))
631                         current_col--;
632                 else {
633                         printf("nah, not possible\n");
634                         return;
635                 }
636                 if (CUR_CELL->currently_focused != NULL) {
637                         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE,
638                                         CUR_CELL->currently_focused->child, XCB_CURRENT_TIME);
639                         render_layout(connection);
640                         xcb_flush(connection);
641                 }
642
643         } else {
644                 printf("direction unhandled\n");
645         }
646 }
647
648 /*
649  * Tries to move the window inside its current container.
650  *
651  * Returns true if the window could be moved, false otherwise.
652  *
653  */
654 static bool move_current_window_in_container(xcb_connection_t *connection, Client *client,
655                 direction_t direction) {
656         Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) :
657                                                 CIRCLEQ_NEXT(client, clients));
658
659         if (other == CIRCLEQ_END(&(client->container->clients)))
660                 return false;
661
662         printf("i can do that\n");
663         /* We can move the client inside its current container */
664         CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
665         if (direction == D_UP)
666                 CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients);
667         else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients);
668         render_layout(connection);
669         return true;
670 }
671
672 /*
673  * Moves the current window to the given direction, creating a column/row if
674  * necessary
675  *
676  */
677 static void move_current_window(xcb_connection_t *connection, direction_t direction) {
678         printf("moving window to direction %d\n", direction);
679         /* Get current window */
680         Container *container = CUR_CELL,
681                   *new;
682
683         /* There has to be a container, see focus_window() */
684         assert(container != NULL);
685
686         /* If there is no window, we’re done */
687         if (container->currently_focused == NULL)
688                 return;
689
690         /* As soon as the client is moved away, the next client in the old
691          * container needs to get focus, if any. Therefore, we save it here. */
692         Client *current_client = container->currently_focused;
693         Client *to_focus = CIRCLEQ_NEXT(current_client, clients);
694         if (to_focus == CIRCLEQ_END(&(container->clients)))
695                 to_focus = NULL;
696
697         switch (direction) {
698                 case D_LEFT:
699                         if (current_col == 0)
700                                 return;
701
702                         new = table[--current_col][current_row];
703                         break;
704                 case D_RIGHT:
705                         if (current_col == (table_dims.x-1))
706                                 expand_table_cols();
707
708                         new = table[++current_col][current_row];
709                         break;
710                 case D_UP:
711                         if (move_current_window_in_container(connection, current_client, D_UP) ||
712                                 current_row == 0)
713                                 return;
714
715                         new = table[current_col][--current_row];
716                         break;
717                 case D_DOWN:
718                         if (move_current_window_in_container(connection, current_client, D_DOWN))
719                                 return;
720
721                         if (current_row == (table_dims.y-1))
722                                 expand_table_rows();
723
724                         new = table[current_col][++current_row];
725                         break;
726         }
727
728         /* Remove it from the old container and put it into the new one */
729         CIRCLEQ_REMOVE(&(container->clients), current_client, clients);
730         CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients);
731
732         /* Update data structures */
733         current_client->container = new;
734         container->currently_focused = to_focus;
735         new->currently_focused = current_client;
736
737         /* TODO: delete all empty columns/rows */
738
739         render_layout(connection);
740 }
741
742 int format_event(xcb_generic_event_t *e)
743 {           
744     uint8_t sendEvent;
745     uint16_t seqnum;
746
747     sendEvent = (e->response_type & 0x80) ? 1 : 0;
748     e->response_type &= ~0x80;
749     seqnum = *((uint16_t *) e + 1);
750
751     switch(e->response_type) 
752     {   
753     case 0:
754         printf("Error %s on seqnum %d (%s).\n",
755             labelError[*((uint8_t *) e + 1)],
756             seqnum,
757             labelRequest[*((uint8_t *) e + 10)]);
758         break;
759     default:
760         printf("Event %s following seqnum %d%s.\n",
761             labelEvent[e->response_type],
762             seqnum,
763             labelSendEvent[sendEvent]);
764         break;  
765     case XCB_KEYMAP_NOTIFY:
766         printf("Event %s%s.\n",
767             labelEvent[e->response_type],
768             labelSendEvent[sendEvent]);
769         break;
770     }
771
772     fflush(stdout);
773     return 1;
774 }
775
776
777 static int handleEvent(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e)
778 {
779         return format_event(e);
780 }
781
782 /*
783  * Starts the given application with the given args.
784  *
785  */
786 static void start_application(char *path, char *args) {
787         pid_t pid;
788         if ((pid = vfork()) == 0) {
789                 /* This is the child */
790                 char *argv[2];
791                 /* TODO: For now, we ignore args. Later on, they should be parsed
792                    correctly (like in the shell?) */
793                 argv[0] = path;
794                 argv[1] = NULL;
795                 execve(path, argv, environment);
796                 /* not reached */
797         }
798 }
799
800 /*
801  * There was a key press. We lookup the key symbol and see if there are any bindings
802  * on that. This allows to do things like binding special characters (think of Ã¤) to
803  * functions to get one more modifier while not losing AltGr :-)
804  *
805  */
806 static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
807         /* FIXME: We need to translate the keypress + state into a string (like, Ã¤)
808            because they do not generate keysyms (use xev and see for yourself) */
809
810         printf("oh yay!\n");
811         printf("gots press %d\n", event->detail);
812
813         /* We need to get the keysym group (There are group 1 to group 4, each holding
814            two keysyms (without shift and with shift) using Xkb because X fails to
815            provide them reliably (it works in Xephyr, it does not in real X) */
816         XkbStateRec state;
817         if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success) {
818                 if (state.group+1 == 2)
819                         event->state |= 0x2;
820         }
821         printf("i'm in state %d\n", event->state);
822
823
824         xcb_key_symbols_t *keysyms = xcb_key_symbols_alloc(conn);
825
826         xcb_keysym_t k0 = xcb_key_symbols_get_keysym(keysyms, event->detail, event->state);
827         if (k0 == XCB_NONE)
828                 printf("couldn't get k0\n");
829
830         printf("gots keysym %d and \n", k0);
831
832
833         /* 30 = u
834          * 57 = n
835          * 27 = r
836          * 28 = t
837          * 40 = d
838          *
839          * â€¦uhm, I should probably say that I’ve remapped my keys in hardware :)
840          */
841         direction_t direction;
842         if (event->detail == 30) {
843                 /* 'u' */
844                 start_application(TERMINAL, NULL);
845
846                 return 1;
847         } else if (event->detail == 57) {
848                 direction = D_LEFT;
849         } else if (event->detail == 27) {
850                 direction = D_DOWN;
851         } else if (event->detail == 28) {
852                 direction = D_UP;
853         } else if (event->detail == 40) {
854                 direction = D_RIGHT;
855         } else if (event->detail == 25) {
856                 Container *con = CUR_CELL;
857                 if (con->colspan == 1)
858                         con->colspan++;
859                 else con->colspan--;
860                 render_layout(conn);
861                 xcb_flush(conn);
862                 return 1;
863         } else {
864                 printf("don't want this.\n");
865                 return 1;
866         }
867
868         /* TODO: ctrl -> focus_container(conn, direction) */
869         /* FIXME: actually wrong but i'm too lazy to grab my keys all the time */
870         if (event->state & XCB_MOD_MASK_CONTROL) {
871                 move_current_window(conn, direction);
872         } else if (event->state & XCB_MOD_MASK_1)
873                 focus_window(conn, direction);
874         /* TODO: shift -> move_current_window(conn, direction) */
875         /* TODO: shift + ctrl -> move_current_container(conn, direction) */
876
877         return 1;
878 }
879
880 /*
881  * When the user moves the mouse pointer onto a window, this callback gets called.
882  *
883  */
884 static int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
885         /* This was either a focus for a client’s parent (= titlebar)… */
886         Client *client = table_get(byParent, event->event),
887                *old_client;
888         /* â€¦or the client itself */
889         if (client == NULL)
890                 client = table_get(byChild, event->event);
891
892         /* If not, then this event is not interesting. This should not happen */
893         if (client == NULL) {
894                 printf("DEBUG: Uninteresting enter_notify-event?\n");
895                 return 1;
896         }
897
898         /* Update container */
899         old_client = client->container->currently_focused;
900         client->container->currently_focused = client;
901
902         current_col = client->container->col;
903         current_row = client->container->row;
904
905         /* Set focus to the entered window, and flush xcb buffer immediately */
906         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
907         /* Update last/current client’s titlebar */
908         if (old_client != NULL)
909                 decorate_window(conn, old_client);
910         decorate_window(conn, client);
911         xcb_flush(conn);
912
913         return 1;
914 }
915
916
917 int handle_map_notify_event(void *prophs, xcb_connection_t *c, xcb_map_notify_event_t *e)
918 {
919         window_attributes_t wa = { TAG_VALUE };
920         wa.u.override_redirect = e->override_redirect;
921         printf("MapNotify for 0x%08x.\n", e->window);
922         manage_window(prophs, c, e->window, wa);
923         return 1;
924 }
925
926 /*
927  * Our window decorations were unmapped. That means, the window will be killed now,
928  * so we better clean up before.
929  *
930  */
931 int handle_unmap_notify_event(void *data, xcb_connection_t *c, xcb_unmap_notify_event_t *e) {
932         Client *client = table_remove(byChild, e->event);
933         xcb_window_t root;
934         printf("UnmapNotify for 0x%08x (received from 0x%08x): ", e->window, e->event);
935         if(!client)
936         {
937                 printf("not a managed window. Ignoring.\n");
938                 return 0;
939         }
940
941         int rows, cols;
942         Client *con_client;
943         for (cols = 0; cols < table_dims.x; cols++)
944                 for (rows = 0; rows < table_dims.y; rows++)
945                         CIRCLEQ_FOREACH(con_client, &(table[cols][rows]->clients), clients)
946                                 if (con_client == client) {
947                                         printf("removing from container\n");
948                                         if (client->container->currently_focused == client)
949                                                 client->container->currently_focused = NULL;
950                                         CIRCLEQ_REMOVE(&(table[cols][rows]->clients), con_client, clients);
951                                         break;
952                                 }
953
954
955
956         root = xcb_setup_roots_iterator(xcb_get_setup(c)).data->root;
957         printf("child of 0x%08x.\n", client->frame);
958         xcb_reparent_window(c, client->child, root, 0, 0);
959         xcb_destroy_window(c, client->frame);
960         xcb_flush(c);
961         table_remove(byParent, client->frame);
962         free(client);
963
964         render_layout(c);
965
966         return 1;
967 }
968
969
970
971 static int handleExposeEvent(void *data, xcb_connection_t *c, xcb_expose_event_t *e) {
972 printf("exposeevent\n");
973         Client *client = table_get(byParent, e->window);
974         if(!client || e->count != 0)
975                 return 1;
976         decorate_window(c, client);
977         return 1;
978 }
979 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
980         xcb_query_tree_cookie_t wintree;
981         xcb_query_tree_reply_t *rep;
982         int i, len;
983         xcb_window_t *children;
984         xcb_get_window_attributes_cookie_t *cookies;
985
986         wintree = xcb_query_tree(c, root);
987         rep = xcb_query_tree_reply(c, wintree, 0);
988         if(!rep)
989                 return;
990         len = xcb_query_tree_children_length(rep);
991         cookies = malloc(len * sizeof(*cookies));
992         if(!cookies)
993         {
994                 free(rep);
995                 return;
996         }
997         children = xcb_query_tree_children(rep);
998         for(i = 0; i < len; ++i)
999                 cookies[i] = xcb_get_window_attributes(c, children[i]);
1000         for(i = 0; i < len; ++i)
1001         {
1002                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
1003                 manage_window(prophs, c, children[i], wa);
1004         }
1005         free(rep);
1006 }
1007
1008 int main(int argc, char *argv[], char *env[]) {
1009         int i, e = 0;
1010
1011         for (i = 0; (env[i] != NULL); i++)
1012                 if (strncmp(env[i], "LC_", strlen("LC_")) == 0 ||
1013                         strncmp(env[i], "LANG=", strlen("LANG=")) == 0 ||
1014                         strncmp(env[i], "DISPLAY=", strlen("DISPLAY=")) == 0) {
1015                         printf("Passing environment \"%s\"\n", env[i]);
1016                         environment = realloc(environment, sizeof(char*) * ++e);
1017                         environment[e-1] = env[i];
1018                 }
1019
1020         /* environment has to be NULL-terminated */
1021         environment = realloc(environment, sizeof(char*) * ++e);
1022         environment[e-1] = NULL;
1023
1024         init_table();
1025
1026         xcb_connection_t *c;
1027         xcb_event_handlers_t evenths;
1028         xcb_property_handlers_t prophs;
1029         xcb_window_t root;
1030
1031         int screens;
1032
1033         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
1034         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
1035
1036         byChild = alloc_table();
1037         byParent = alloc_table();
1038
1039         c = xcb_connect(NULL, &screens);
1040
1041         printf("x screen is %d\n", screens);
1042
1043         int major, minor, error;
1044
1045         major = XkbMajorVersion;
1046         minor = XkbMinorVersion;
1047
1048         int evBase, errBase;
1049
1050         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
1051                 fprintf(stderr, "XkbOpenDisplay() failed\n");
1052                 return 1;
1053         }
1054
1055         int i1;
1056         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
1057                 fprintf(stderr, "XKB not supported by X-server\n");
1058                 return 1;
1059         }
1060
1061         /* Font loading */
1062         myfont = load_font(c, pattern);
1063
1064         xcb_event_handlers_init(c, &evenths);
1065         for(i = 2; i < 128; ++i)
1066                 xcb_event_set_handler(&evenths, i, handleEvent, 0);
1067
1068         for(i = 0; i < 256; ++i)
1069                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t) handleEvent, 0);
1070
1071         /* Expose = an Application should redraw itself. That is, we have to redraw our
1072          * contents (= top/bottom bar, titlebars for each window) */
1073         xcb_event_set_expose_handler(&evenths, handleExposeEvent, 0);
1074
1075         /* Key presses are pretty obvious, I think */
1076         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
1077
1078         /* Enter window = user moved his mouse over the window */
1079         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
1080
1081         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
1082
1083         xcb_property_handlers_init(&prophs, &evenths);
1084         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
1085
1086         root = xcb_aux_get_screen(c, screens)->root;
1087         root_win = root;
1088
1089         uint32_t mask = XCB_CW_EVENT_MASK;
1090         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE };
1091         xcb_change_window_attributes(c, root, mask, values);
1092
1093         /* Grab 'a' */
1094         //xcb_grab_key(c, 0, root, 0, 38, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1095
1096         xcb_grab_key(c, 0, root, 0, 30, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1097         xcb_grab_key(c, 0, root, 0, 38, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1098
1099
1100         xcb_grab_key(c, 0, root, XCB_MOD_MASK_1, 57, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1101         xcb_grab_key(c, 0, root, XCB_MOD_MASK_1, 28, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1102         xcb_grab_key(c, 0, root, XCB_MOD_MASK_1, 27, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1103         xcb_grab_key(c, 0, root, XCB_MOD_MASK_1, 40, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1104
1105         xcb_grab_key(c, 0, root, XCB_MOD_MASK_CONTROL, 57, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1106         xcb_grab_key(c, 0, root, XCB_MOD_MASK_CONTROL, 28, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1107         xcb_grab_key(c, 0, root, XCB_MOD_MASK_CONTROL, 27, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1108         xcb_grab_key(c, 0, root, XCB_MOD_MASK_CONTROL, 40, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1109         xcb_grab_key(c, 0, root, XCB_MOD_MASK_CONTROL, 25, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1110
1111
1112
1113         //xcb_grab_key(c, 0, root, XCB_BUTTON_MASK_ANY, 40, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1114         start_application(TERMINAL, NULL);
1115
1116         xcb_flush(c);
1117
1118         manage_existing_windows(c, &prophs, root);
1119
1120         xcb_event_wait_for_event_loop(&evenths);
1121
1122         /* not reached */
1123         return 0;
1124 }