]> git.sur5r.net Git - i3/i3/blob - mainx.c
Bugfixes in window placement
[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 Display *xkbdpy;
30
31 TAILQ_HEAD(bindings_head, Binding) bindings;
32
33 static const int TOP = 20;
34 static const int LEFT = 5;
35 static const int BOTTOM = 5;
36 static const int RIGHT = 5;
37
38 /* This is the filtered environment which will be passed to opened applications.
39  * It contains DISPLAY (naturally) and locales stuff (LC_*, LANG) */
40 static char **environment;
41
42 /* hm, xcb_wm wants us to implement this. */
43 table_t *byChild = 0;
44 table_t *byParent = 0;
45 xcb_window_t root_win;
46
47 char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
48
49
50 int current_col = 0;
51 int current_row = 0;
52
53
54 int globalc = 0;
55
56
57 static const char *labelError[] = {
58     "Success",
59     "BadRequest",
60     "BadValue",
61     "BadWindow",
62     "BadPixmap",
63     "BadAtom",
64     "BadCursor",
65     "BadFont",
66     "BadMatch",
67     "BadDrawable",
68     "BadAccess",
69     "BadAlloc",
70     "BadColor",
71     "BadGC",
72     "BadIDChoice",
73     "BadName",
74     "BadLength",
75     "BadImplementation",
76 };
77
78 static const char *labelRequest[] = {
79     "no request",
80     "CreateWindow",
81     "ChangeWindowAttributes",
82     "GetWindowAttributes",
83     "DestroyWindow",
84     "DestroySubwindows",
85     "ChangeSaveSet",
86     "ReparentWindow",
87     "MapWindow",
88     "MapSubwindows",
89     "UnmapWindow",
90     "UnmapSubwindows",
91     "ConfigureWindow",
92     "CirculateWindow",
93     "GetGeometry",
94     "QueryTree",
95     "InternAtom",
96     "GetAtomName",
97     "ChangeProperty",
98     "DeleteProperty",
99     "GetProperty",
100     "ListProperties",
101     "SetSelectionOwner",
102     "GetSelectionOwner",
103     "ConvertSelection",
104     "SendEvent",
105     "GrabPointer",
106     "UngrabPointer",
107     "GrabButton",
108     "UngrabButton",
109     "ChangeActivePointerGrab",
110     "GrabKeyboard",
111     "UngrabKeyboard",
112     "GrabKey",
113     "UngrabKey",
114     "AllowEvents",
115     "GrabServer",
116     "UngrabServer",
117     "QueryPointer",
118     "GetMotionEvents",
119     "TranslateCoords",
120     "WarpPointer",
121     "SetInputFocus",
122     "GetInputFocus",
123     "QueryKeymap",
124     "OpenFont",
125     "CloseFont",
126     "QueryFont",
127     "QueryTextExtents",
128     "ListFonts",
129     "ListFontsWithInfo",
130     "SetFontPath",
131     "GetFontPath",
132     "CreatePixmap",
133     "FreePixmap",
134     "CreateGC",
135     "ChangeGC",
136     "CopyGC",
137     "SetDashes",
138     "SetClipRectangles",
139     "FreeGC",
140     "ClearArea",
141     "CopyArea",
142     "CopyPlane",
143     "PolyPoint",
144     "PolyLine",
145     "PolySegment",
146     "PolyRectangle",
147     "PolyArc",
148     "FillPoly",
149     "PolyFillRectangle",
150     "PolyFillArc",
151     "PutImage",
152     "GetImage",
153     "PolyText",
154     "PolyText",
155     "ImageText",
156     "ImageText",
157     "CreateColormap",
158     "FreeColormap",
159     "CopyColormapAndFree",
160     "InstallColormap",
161     "UninstallColormap",
162     "ListInstalledColormaps",
163     "AllocColor",
164     "AllocNamedColor",
165     "AllocColorCells",
166     "AllocColorPlanes",
167     "FreeColors",
168     "StoreColors",
169     "StoreNamedColor",
170     "QueryColors",
171     "LookupColor",
172     "CreateCursor",
173     "CreateGlyphCursor",
174     "FreeCursor",
175     "RecolorCursor",
176     "QueryBestSize",
177     "QueryExtension",
178     "ListExtensions",
179     "ChangeKeyboardMapping",
180     "GetKeyboardMapping",
181     "ChangeKeyboardControl",
182     "GetKeyboardControl",
183     "Bell",
184     "ChangePointerControl",
185     "GetPointerControl",
186     "SetScreenSaver",
187     "GetScreenSaver",
188     "ChangeHosts",
189     "ListHosts",
190     "SetAccessControl",
191     "SetCloseDownMode",
192     "KillClient",
193     "RotateProperties",
194     "ForceScreenSaver",
195     "SetPointerMapping",
196     "GetPointerMapping",
197     "SetModifierMapping",
198     "GetModifierMapping",
199     "major 120",
200     "major 121",
201     "major 122",
202     "major 123",
203     "major 124",
204     "major 125",
205     "major 126",
206     "NoOperation",
207 };
208
209 static const char *labelEvent[] = {
210     "error",
211     "reply",
212     "KeyPress",
213     "KeyRelease",
214     "ButtonPress",
215     "ButtonRelease",
216     "MotionNotify",
217     "EnterNotify",
218     "LeaveNotify",
219     "FocusIn",
220     "FocusOut",
221     "KeymapNotify",
222     "Expose",
223     "GraphicsExpose",
224     "NoExpose",
225     "VisibilityNotify",
226     "CreateNotify",
227     "DestroyNotify",
228     "UnmapNotify",
229     "MapNotify",
230     "MapRequest",
231     "ReparentNotify",
232     "ConfigureNotify",
233     "ConfigureRequest",
234     "GravityNotify",
235     "ResizeRequest",
236     "CirculateNotify",
237     "CirculateRequest",
238     "PropertyNotify",
239     "SelectionClear",
240     "SelectionRequest",
241     "SelectionNotify",
242     "ColormapNotify",
243     "ClientMessage",
244     "MappingNotify",
245 };
246
247 static const char *labelSendEvent[] = {
248     "",
249     " (from SendEvent)",
250 };
251
252 /*
253  *
254  * TODO: what exactly does this, what happens if we leave stuff out?
255  *
256  */
257 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa)
258 {
259         printf("managing window.\n");
260         xcb_drawable_t d = { window };
261         xcb_get_geometry_cookie_t geomc;
262         xcb_get_geometry_reply_t *geom;
263         xcb_get_window_attributes_reply_t *attr = 0;
264         if(wa.tag == TAG_COOKIE)
265         {
266                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
267                 if(!attr)
268                         return;
269                 if(attr->map_state != XCB_MAP_STATE_VIEWABLE)
270                 {
271                         printf("Window 0x%08x is not mapped. Ignoring.\n", window);
272                         free(attr);
273                         return;
274                 }
275                 wa.tag = TAG_VALUE;
276                 wa.u.override_redirect = attr->override_redirect;
277         }
278         if(!wa.u.override_redirect && table_get(byChild, window))
279         {
280                 printf("Window 0x%08x already managed. Ignoring.\n", window);
281                 free(attr);
282                 return;
283         }
284         if(wa.u.override_redirect)
285         {
286                 printf("Window 0x%08x has override-redirect set. Ignoring.\n", window);
287                 free(attr);
288                 return;
289         }
290         geomc = xcb_get_geometry(c, d);
291         if(!attr)
292         {
293                 wa.tag = TAG_COOKIE;
294                 wa.u.cookie = xcb_get_window_attributes(c, window);
295                 attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0);
296         }
297         geom = xcb_get_geometry_reply(c, geomc, 0);
298         if(attr && geom)
299         {
300                 reparent_window(c, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height);
301                 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
302         }
303         free(attr);
304         free(geom);
305 }
306
307
308 /*
309  * Returns the colorpixel to use for the given hex color (think of HTML).
310  *
311  * The hex_color has to start with #, for example #FF00FF.
312  *
313  * NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
314  * This has to be done by the caller.
315  *
316  */
317 uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex) {
318         #define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255)
319         char strgroups[3][3] = {{hex[1], hex[2], '\0'},
320                                 {hex[3], hex[4], '\0'},
321                                 {hex[5], hex[6], '\0'}};
322         int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)),
323                         RGB_8_TO_16(strtol(strgroups[1], NULL, 16)),
324                         RGB_8_TO_16(strtol(strgroups[2], NULL, 16))};
325
326         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
327
328         xcb_colormap_t colormapId = xcb_generate_id(conn);
329         xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, colormapId, window, root_screen->root_visual);
330         xcb_alloc_color_reply_t *reply = xcb_alloc_color_reply(conn,
331                         xcb_alloc_color(conn, colormapId, rgb16[0], rgb16[1], rgb16[2]), NULL);
332
333         if (!reply) {
334                 printf("color fail\n");
335                 exit(1);
336         }
337
338         uint32_t pixel = reply->pixel;
339         free(reply);
340         xcb_free_colormap(conn, colormapId);
341         return pixel;
342 }
343
344 /*
345  * (Re-)draws window decorations for a given Client
346  *
347  */
348 void decorate_window(xcb_connection_t *conn, Client *client) {
349         uint32_t mask = 0;
350         uint32_t values[3];
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: rewrite this block so that the need to puke vanishes :) */
430                         /* TODO: at the moment, every column/row is 200px. This
431                          * needs to be changed to "percentage of the screen" by
432                          * default and adjustable by the user if necessary.
433                          */
434                         values[0] = container->col * container->width; /* x */
435                         values[1] = container->row * container->height +
436                                 (container->height / num_clients) * current_client; /* y */
437
438                         if (client->x != values[0] || client->y != values[1]) {
439                                 printf("frame needs to be pushed to %dx%d\n",
440                                                 values[0], values[1]);
441                                 client->x = values[0];
442                                 client->y = values[1];
443                                 xcb_configure_window(connection, client->frame,
444                                                 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
445                         }
446                         /* TODO: vertical default layout */
447                         values[0] = container->width; /* width */
448                         values[1] = container->height / num_clients; /* height */
449
450                         if (client->width != values[0] || client->height != values[1]) {
451                                 client->width = values[0];
452                                 client->height = values[1];
453                                 xcb_configure_window(connection, client->frame,
454                                                 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
455                         }
456
457                         /* TODO: hmm, only do this for new wins */
458                         /* The coordinates of the child are relative to its frame, we
459                          * add a border of 2 pixel to each value */
460                         values[0] = 2;
461                         values[1] = font->height + 2 + 2;
462                         values[2] = client->width - (values[0] + 2);
463                         values[3] = client->height - (values[1] + 2);
464                         printf("child itself will be at %dx%d with size %dx%d\n",
465                                         values[0], values[1], values[2], values[3]);
466
467                         xcb_configure_window(connection, client->child, mask, values);
468
469                         decorate_window(connection, client);
470                         current_client++;
471                 }
472         } else {
473                 /* TODO: Implement stacking */
474         }
475 }
476
477 void render_layout(xcb_connection_t *conn) {
478         int cols, rows;
479         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
480         int width = root_screen->width_in_pixels;
481         int height = root_screen->height_in_pixels;
482
483         int num_cols = table_dims.x, num_rows = table_dims.y;
484
485         printf("got %d rows and %d cols\n", num_rows, num_cols);
486         printf("each of them therefore is %d px width and %d px height\n",
487                         width / num_cols, height / num_rows);
488
489         /* Go through the whole table and render what’s necessary */
490         for (cols = 0; cols < table_dims.x; cols++)
491                 for (rows = 0; rows < table_dims.y; rows++)
492                         if (table[cols][rows] != NULL) {
493                                 Container *con = table[cols][rows];
494                                 printf("container has %d colspan, %d rowspan\n",
495                                                 con->colspan, con->rowspan);
496                                 /* Update position of the container */
497                                 con->row = rows;
498                                 con->col = cols;
499                                 con->width = (width / num_cols) * con->colspan;
500                                 con->height = (height / num_rows) * con->rowspan;
501
502                                 /* Render it */
503                                 render_container(conn, table[cols][rows]);
504                         }
505
506         xcb_flush(conn);
507 }
508
509 /*
510  * Let’s own this window…
511  *
512  */
513 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
514                 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
515                 int16_t x, int16_t y, uint16_t width, uint16_t height) {
516
517         Client *new = table_get(byChild, child);
518         if (new == NULL) {
519                 printf("oh, it's new\n");
520                 new = calloc(sizeof(Client), 1);
521                 new->x = -1;
522                 new->y = -1;
523         }
524         uint32_t mask = 0;
525         uint32_t values[3];
526
527         /* Insert into the currently active container */
528         CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
529
530         printf("currently_focused = %p\n", new);
531         CUR_CELL->currently_focused = new;
532         new->container = CUR_CELL;
533
534         new->frame = xcb_generate_id(conn);
535         new->child = child;
536         new->width = width;
537         new->height = height;
538
539         /* Don’t generate events for our new window, it should *not* be managed */
540         mask |= XCB_CW_OVERRIDE_REDIRECT;
541         values[0] = 1;
542
543         /* We want to know when… */
544         mask |= XCB_CW_EVENT_MASK;
545         values[1] =     XCB_EVENT_MASK_BUTTON_PRESS | /* â€¦mouse is pressed/released */
546                         XCB_EVENT_MASK_BUTTON_RELEASE |
547                         XCB_EVENT_MASK_EXPOSURE | /* â€¦our window needs to be redrawn */
548                         XCB_EVENT_MASK_ENTER_WINDOW /* â€¦user moves cursor inside our window */;
549
550         printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
551
552         /* Yo dawg, I heard you like windows, so I create a window around your window… */
553         xcb_create_window(conn,
554                         depth,
555                         new->frame,
556                         root,
557                         x,
558                         y,
559                         width + LEFT + RIGHT,
560                         height + TOP + BOTTOM,
561                         /* border_width */ 0,
562                         XCB_WINDOW_CLASS_INPUT_OUTPUT,
563                         visual,
564                         mask,
565                         values);
566         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
567
568         /* Map the window on the screen (= make it visible) */
569         xcb_map_window(conn, new->frame);
570
571         /* Generate a graphics context for the titlebar */
572         new->titlegc = xcb_generate_id(conn);
573         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
574
575         /* Draw decorations */
576         decorate_window(conn, new);
577
578         /* Put our data structure (Client) into the table */
579         table_put(byParent, new->frame, new);
580         table_put(byChild, child, new);
581
582         /* Moves the original window into the new frame we've created for it */
583         i3Font *font = load_font(conn, pattern);
584         xcb_reparent_window(conn, child, new->frame, 0, font->height);
585
586         /* We are interested in property changes */
587         mask = XCB_CW_EVENT_MASK;
588         values[0] =     XCB_EVENT_MASK_PROPERTY_CHANGE |
589                         XCB_EVENT_MASK_STRUCTURE_NOTIFY |
590                         XCB_EVENT_MASK_ENTER_WINDOW;
591         xcb_change_window_attributes(conn, child, mask, values);
592
593         /* Focus the new window */
594         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_NONE, new->child, XCB_CURRENT_TIME);
595
596         render_layout(conn);
597 }
598
599 static bool focus_window_in_container(xcb_connection_t *connection, Container *container,
600                 direction_t direction) {
601         /* If this container is empty, we’re done */
602         if (container->currently_focused == NULL)
603                 return false;
604
605         Client *candidad;
606         if (direction == D_UP)
607                 candidad = CIRCLEQ_PREV(container->currently_focused, clients);
608         else if (direction == D_DOWN)
609                 candidad = CIRCLEQ_NEXT(container->currently_focused, clients);
610
611         /* If we don’t have anything to select, we’re done */
612         if (candidad == CIRCLEQ_END(&(container->clients)))
613                 return false;
614
615         /* Set focus if we could successfully move */
616         container->currently_focused = candidad;
617         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE, candidad->child, XCB_CURRENT_TIME);
618         render_layout(connection);
619
620         return true;
621 }
622
623 static void focus_window(xcb_connection_t *connection, direction_t direction) {
624         printf("focusing direction %d\n", direction);
625         /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
626         if (direction == D_UP || direction == D_DOWN) {
627                 /* Let’s see if we can perform up/down focus in the current container */
628                 Container *container = CUR_CELL;
629
630                 /* There always is a container. If not, current_col or current_row is wrong */
631                 assert(container != NULL);
632
633                 if (focus_window_in_container(connection, container, direction))
634                         return;
635         } else if (direction == D_LEFT || direction == D_RIGHT) {
636                 if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
637                         current_col++;
638                 else if (direction == D_LEFT && cell_exists(current_col-1, current_row))
639                         current_col--;
640                 else {
641                         printf("nah, not possible\n");
642                         return;
643                 }
644                 if (CUR_CELL->currently_focused != NULL) {
645                         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE,
646                                         CUR_CELL->currently_focused->child, XCB_CURRENT_TIME);
647                         render_layout(connection);
648                 }
649
650         } else {
651                 printf("direction unhandled\n");
652         }
653 }
654
655 /*
656  * Tries to move the window inside its current container.
657  *
658  * Returns true if the window could be moved, false otherwise.
659  *
660  */
661 static bool move_current_window_in_container(xcb_connection_t *connection, Client *client,
662                 direction_t direction) {
663         Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) :
664                                                 CIRCLEQ_NEXT(client, clients));
665
666         if (other == CIRCLEQ_END(&(client->container->clients)))
667                 return false;
668
669         printf("i can do that\n");
670         /* We can move the client inside its current container */
671         CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
672         if (direction == D_UP)
673                 CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients);
674         else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients);
675         render_layout(connection);
676         return true;
677 }
678
679 /*
680  * Moves the current window to the given direction, creating a column/row if
681  * necessary
682  *
683  */
684 static void move_current_window(xcb_connection_t *connection, direction_t direction) {
685         printf("moving window to direction %d\n", direction);
686         /* Get current window */
687         Container *container = CUR_CELL,
688                   *new;
689
690         /* There has to be a container, see focus_window() */
691         assert(container != NULL);
692
693         /* If there is no window, we’re done */
694         if (container->currently_focused == NULL)
695                 return;
696
697         /* As soon as the client is moved away, the next client in the old
698          * container needs to get focus, if any. Therefore, we save it here. */
699         Client *current_client = container->currently_focused;
700         Client *to_focus = CIRCLEQ_NEXT(current_client, clients);
701         if (to_focus == CIRCLEQ_END(&(container->clients)))
702                 to_focus = NULL;
703
704         switch (direction) {
705                 case D_LEFT:
706                         if (current_col == 0)
707                                 return;
708
709                         new = table[--current_col][current_row];
710                         break;
711                 case D_RIGHT:
712                         if (current_col == (table_dims.x-1))
713                                 expand_table_cols();
714
715                         new = table[++current_col][current_row];
716                         break;
717                 case D_UP:
718                         if (move_current_window_in_container(connection, current_client, D_UP) ||
719                                 current_row == 0)
720                                 return;
721
722                         new = table[current_col][--current_row];
723                         break;
724                 case D_DOWN:
725                         if (move_current_window_in_container(connection, current_client, D_DOWN))
726                                 return;
727
728                         if (current_row == (table_dims.y-1))
729                                 expand_table_rows();
730
731                         new = table[current_col][++current_row];
732                         break;
733         }
734
735         /* Remove it from the old container and put it into the new one */
736         CIRCLEQ_REMOVE(&(container->clients), current_client, clients);
737         CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients);
738
739         /* Update data structures */
740         current_client->container = new;
741         container->currently_focused = to_focus;
742         new->currently_focused = current_client;
743
744         /* TODO: delete all empty columns/rows */
745
746         render_layout(connection);
747 }
748
749 /*
750  * "Snaps" the current container (not possible for windows, because it works at table base)
751  * to the given direction, that is, adjusts cellspan/rowspan
752  *
753  */
754 static void snap_current_container(xcb_connection_t *connection, direction_t direction) {
755         printf("snapping container to direction %d\n", direction);
756
757         Container *container = CUR_CELL;
758         int i;
759
760         assert(container != NULL);
761
762         switch (direction) {
763                 case D_LEFT:
764                         /* Snap to the left is actually a move to the left and then a snap right */
765                         move_current_window(connection, D_LEFT);
766                         snap_current_container(connection, D_RIGHT);
767                         return;
768                 case D_RIGHT:
769                         /* Check if the cell is used */
770                         if (!cell_exists(container->col + 1, container->row) ||
771                                 table[container->col+1][container->row]->currently_focused != NULL) {
772                                 printf("cannot snap to right - the cell is already used\n");
773                                 return;
774                         }
775
776                         /* Check if there are other cells with rowspan, which are in our way.
777                          * If so, reduce their rowspan. */
778                         for (i = container->row-1; i >= 0; i--) {
779                                 printf("we got cell %d, %d with rowspan %d\n",
780                                                 container->col+1, i, table[container->col+1][i]->rowspan);
781                                 while ((table[container->col+1][i]->rowspan-1) >= (container->row - i))
782                                         table[container->col+1][i]->rowspan--;
783                                 printf("new rowspan = %d\n", table[container->col+1][i]->rowspan);
784                         }
785
786                         container->colspan++;
787                         break;
788                 case D_UP:
789                         move_current_window(connection, D_UP);
790                         snap_current_container(connection, D_DOWN);
791                         return;
792                 case D_DOWN:
793                         printf("snapping down\n");
794                         if (!cell_exists(container->col, container->row+1) ||
795                                 table[container->col][container->row+1]->currently_focused != NULL) {
796                                 printf("cannot snap down - the cell is already used\n");
797                                 return;
798                         }
799
800                         for (i = container->col-1; i >= 0; i--) {
801                                 printf("we got cell %d, %d with colspan %d\n",
802                                                 i, container->row+1, table[i][container->row+1]->colspan);
803                                 while ((table[i][container->row+1]->colspan-1) >= (container->col - i))
804                                         table[i][container->row+1]->colspan--;
805                                 printf("new colspan = %d\n", table[i][container->row+1]->colspan);
806
807                         }
808
809                         container->rowspan++;
810                         break;
811         }
812
813         render_layout(connection);
814 }
815
816 int format_event(xcb_generic_event_t *e)
817 {           
818     uint8_t sendEvent;
819     uint16_t seqnum;
820
821     sendEvent = (e->response_type & 0x80) ? 1 : 0;
822     e->response_type &= ~0x80;
823     seqnum = *((uint16_t *) e + 1);
824
825     switch(e->response_type) 
826     {   
827     case 0:
828         printf("Error %s on seqnum %d (%s).\n",
829             labelError[*((uint8_t *) e + 1)],
830             seqnum,
831             labelRequest[*((uint8_t *) e + 10)]);
832         break;
833     default:
834         printf("Event %s following seqnum %d%s.\n",
835             labelEvent[e->response_type],
836             seqnum,
837             labelSendEvent[sendEvent]);
838         break;  
839     case XCB_KEYMAP_NOTIFY:
840         printf("Event %s%s.\n",
841             labelEvent[e->response_type],
842             labelSendEvent[sendEvent]);
843         break;
844     }
845
846     fflush(stdout);
847     return 1;
848 }
849
850
851 static int handleEvent(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e)
852 {
853         return format_event(e);
854 }
855
856 /*
857  * Starts the given application with the given args.
858  *
859  */
860 static void start_application(char *path, char *args) {
861         pid_t pid;
862         if ((pid = vfork()) == 0) {
863                 /* This is the child */
864                 char *argv[2];
865                 /* TODO: For now, we ignore args. Later on, they should be parsed
866                    correctly (like in the shell?) */
867                 argv[0] = path;
868                 argv[1] = NULL;
869                 execve(path, argv, environment);
870                 /* not reached */
871         }
872 }
873
874 /*
875  * Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
876  * Therefore, we just replay all key presses.
877  *
878  */
879 static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
880         printf("got key release, just passing\n");
881         xcb_allow_events(conn, ReplayKeyboard, event->time);
882         xcb_flush(conn);
883         return 1;
884 }
885
886 /*
887  * Parses a command, see file CMDMODE for more information
888  *
889  */
890 static void parse_command(xcb_connection_t *conn, const char *command) {
891         printf("--- parsing command \"%s\" ---\n", command);
892         /* Hmm, just to be sure */
893         if (command[0] == '\0')
894                 return;
895
896         /* Is it an <exec>? */
897         if (strncmp(command, "exec ", strlen("exec ")) == 0) {
898                 printf("starting \"%s\"\n", command + strlen("exec "));
899                 start_application(command+strlen("exec "), NULL);
900                 return;
901         }
902
903         /* Is it a <with>? */
904         if (command[0] == 'w') {
905                 /* TODO: implement */
906                 printf("not yet implemented.\n");
907                 return;
908         }
909
910         /* It's a normal <cmd> */
911         int times;
912         char *rest = NULL;
913         enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
914         direction_t direction;
915         times = strtol(command, &rest, 10);
916         if (rest == NULL) {
917                 printf("Invalid command: Consists only of a movement\n");
918                 return;
919         }
920         if (*rest == 'm' || *rest == 's') {
921                 action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP);
922                 rest++;
923         }
924
925         /* Now perform action to <where> */
926         while (*rest != '\0') {
927                 /* TODO: tags */
928                 if (*rest == 'h')
929                         direction = D_LEFT;
930                 else if (*rest == 'j')
931                         direction = D_DOWN;
932                 else if (*rest == 'k')
933                         direction = D_UP;
934                 else if (*rest == 'l')
935                         direction = D_RIGHT;
936                 else {
937                         printf("unknown direction: %c\n", *rest);
938                         return;
939                 }
940
941                 if (action == ACTION_FOCUS)
942                         focus_window(conn, direction);
943                 else if (action == ACTION_MOVE)
944                         move_current_window(conn, direction);
945                 else if (action == ACTION_SNAP)
946                         snap_current_container(conn, direction);
947
948                 rest++;
949
950         }
951
952         printf("--- done ---\n");
953 }
954
955 /*
956  * There was a key press. We lookup the key symbol and see if there are any bindings
957  * on that. This allows to do things like binding special characters (think of Ã¤) to
958  * functions to get one more modifier while not losing AltGr :-)
959  * TODO: this description needs to be more understandable
960  *
961  */
962 static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
963         printf("Keypress %d\n", event->detail);
964
965         /* We need to get the keysym group (There are group 1 to group 4, each holding
966            two keysyms (without shift and with shift) using Xkb because X fails to
967            provide them reliably (it works in Xephyr, it does not in real X) */
968         XkbStateRec state;
969         if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
970                 event->state |= 0x2;
971
972         printf("state %d\n", event->state);
973
974         /* Find the binding */
975         Binding *bind, *best_match = TAILQ_END(&bindings);
976         TAILQ_FOREACH(bind, &bindings, bindings) {
977                 if (bind->keycode == event->detail &&
978                         (bind->mods & event->state) == bind->mods) {
979                         if (best_match == TAILQ_END(&bindings) ||
980                                 bind->mods > best_match->mods)
981                                 best_match = bind;
982                 }
983         }
984
985         /* No match? Then it was an actively grabbed key, that is with Mode_switch, and
986            the user did not press Mode_switch, so just pass it… */
987         if (best_match == TAILQ_END(&bindings)) {
988                 xcb_allow_events(conn, ReplayKeyboard, event->time);
989                 xcb_flush(conn);
990                 return 1;
991         }
992
993         if (event->state & 0x2) {
994                 printf("that's mode_switch\n");
995                 parse_command(conn, best_match->command);
996                 printf("ok, hiding this event.\n");
997                 xcb_allow_events(conn, SyncKeyboard, event->time);
998                 xcb_flush(conn);
999                 return 1;
1000         }
1001
1002         parse_command(conn, best_match->command);
1003         return 1;
1004 }
1005
1006 /*
1007  * When the user moves the mouse pointer onto a window, this callback gets called.
1008  *
1009  */
1010 static int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
1011         printf("enter_notify\n");
1012
1013         /* This was either a focus for a client’s parent (= titlebar)… */
1014         Client *client = table_get(byParent, event->event),
1015                *old_client;
1016         /* â€¦or the client itself */
1017         if (client == NULL)
1018                 client = table_get(byChild, event->event);
1019
1020         /* If not, then this event is not interesting. This should not happen */
1021         if (client == NULL) {
1022                 printf("DEBUG: Uninteresting enter_notify-event?\n");
1023                 return 1;
1024         }
1025
1026         /* Update container */
1027         old_client = client->container->currently_focused;
1028         client->container->currently_focused = client;
1029
1030         current_col = client->container->col;
1031         current_row = client->container->row;
1032
1033         /* Set focus to the entered window, and flush xcb buffer immediately */
1034         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_NONE, client->child, XCB_CURRENT_TIME);
1035         /* Update last/current client’s titlebar */
1036         if (old_client != NULL)
1037                 decorate_window(conn, old_client);
1038         decorate_window(conn, client);
1039         xcb_flush(conn);
1040
1041         return 1;
1042 }
1043
1044 int handle_map_notify_event(void *prophs, xcb_connection_t *c, xcb_map_notify_event_t *e)
1045 {
1046         window_attributes_t wa = { TAG_VALUE };
1047         wa.u.override_redirect = e->override_redirect;
1048         printf("MapNotify for 0x%08x.\n", e->window);
1049         manage_window(prophs, c, e->window, wa);
1050         return 1;
1051 }
1052
1053 /*
1054  * Our window decorations were unmapped. That means, the window will be killed now,
1055  * so we better clean up before.
1056  *
1057  */
1058 int handle_unmap_notify_event(void *data, xcb_connection_t *c, xcb_unmap_notify_event_t *e) {
1059         Client *client = table_remove(byChild, e->event);
1060         xcb_window_t root;
1061         printf("UnmapNotify for 0x%08x (received from 0x%08x): ", e->window, e->event);
1062         if(!client)
1063         {
1064                 printf("not a managed window. Ignoring.\n");
1065                 return 0;
1066         }
1067
1068         int rows, cols;
1069         Client *con_client;
1070         for (cols = 0; cols < table_dims.x; cols++)
1071                 for (rows = 0; rows < table_dims.y; rows++)
1072                         CIRCLEQ_FOREACH(con_client, &(table[cols][rows]->clients), clients)
1073                                 if (con_client == client) {
1074                                         printf("removing from container\n");
1075                                         if (client->container->currently_focused == client)
1076                                                 client->container->currently_focused = NULL;
1077                                         CIRCLEQ_REMOVE(&(table[cols][rows]->clients), con_client, clients);
1078                                         break;
1079                                 }
1080
1081
1082
1083         root = xcb_setup_roots_iterator(xcb_get_setup(c)).data->root;
1084         printf("child of 0x%08x.\n", client->frame);
1085         xcb_reparent_window(c, client->child, root, 0, 0);
1086         xcb_destroy_window(c, client->frame);
1087         xcb_flush(c);
1088         table_remove(byParent, client->frame);
1089         free(client);
1090
1091         render_layout(c);
1092
1093         return 1;
1094 }
1095
1096
1097
1098 static int handleExposeEvent(void *data, xcb_connection_t *c, xcb_expose_event_t *e) {
1099 printf("exposeevent\n");
1100         Client *client = table_get(byParent, e->window);
1101         if(!client || e->count != 0)
1102                 return 1;
1103         decorate_window(c, client);
1104         return 1;
1105 }
1106 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
1107         xcb_query_tree_cookie_t wintree;
1108         xcb_query_tree_reply_t *rep;
1109         int i, len;
1110         xcb_window_t *children;
1111         xcb_get_window_attributes_cookie_t *cookies;
1112
1113         wintree = xcb_query_tree(c, root);
1114         rep = xcb_query_tree_reply(c, wintree, 0);
1115         if(!rep)
1116                 return;
1117         len = xcb_query_tree_children_length(rep);
1118         cookies = malloc(len * sizeof(*cookies));
1119         if(!cookies)
1120         {
1121                 free(rep);
1122                 return;
1123         }
1124         children = xcb_query_tree_children(rep);
1125         for(i = 0; i < len; ++i)
1126                 cookies[i] = xcb_get_window_attributes(c, children[i]);
1127         for(i = 0; i < len; ++i)
1128         {
1129                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
1130                 manage_window(prophs, c, children[i], wa);
1131         }
1132         free(rep);
1133 }
1134
1135 int main(int argc, char *argv[], char *env[]) {
1136         int i, e = 0;
1137
1138         for (i = 0; (env[i] != NULL); i++)
1139                 if (strncmp(env[i], "LC_", strlen("LC_")) == 0 ||
1140                         strncmp(env[i], "LANG=", strlen("LANG=")) == 0 ||
1141                         strncmp(env[i], "DISPLAY=", strlen("DISPLAY=")) == 0) {
1142                         printf("Passing environment \"%s\"\n", env[i]);
1143                         environment = realloc(environment, sizeof(char*) * ++e);
1144                         environment[e-1] = env[i];
1145                 }
1146
1147         /* environment has to be NULL-terminated */
1148         environment = realloc(environment, sizeof(char*) * ++e);
1149         environment[e-1] = NULL;
1150
1151         init_table();
1152
1153         xcb_connection_t *c;
1154         xcb_event_handlers_t evenths;
1155         xcb_property_handlers_t prophs;
1156         xcb_window_t root;
1157
1158         int screens;
1159
1160         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
1161         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
1162
1163         byChild = alloc_table();
1164         byParent = alloc_table();
1165
1166         TAILQ_INIT(&bindings);
1167
1168         c = xcb_connect(NULL, &screens);
1169
1170         printf("x screen is %d\n", screens);
1171
1172         /* TODO: this has to be more beautiful somewhen */
1173         int major, minor, error;
1174
1175         major = XkbMajorVersion;
1176         minor = XkbMinorVersion;
1177
1178         int evBase, errBase;
1179
1180         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
1181                 fprintf(stderr, "XkbOpenDisplay() failed\n");
1182                 return 1;
1183         }
1184
1185         int i1;
1186         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
1187                 fprintf(stderr, "XKB not supported by X-server\n");
1188                 return 1;
1189         }
1190         /* end of ugliness */
1191
1192         xcb_event_handlers_init(c, &evenths);
1193         for(i = 2; i < 128; ++i)
1194                 xcb_event_set_handler(&evenths, i, handleEvent, 0);
1195
1196         for(i = 0; i < 256; ++i)
1197                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t) handleEvent, 0);
1198
1199         /* Expose = an Application should redraw itself. That is, we have to redraw our
1200          * contents (= top/bottom bar, titlebars for each window) */
1201         xcb_event_set_expose_handler(&evenths, handleExposeEvent, 0);
1202
1203         /* Key presses/releases are pretty obvious, I think */
1204         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
1205         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
1206
1207         /* Enter window = user moved his mouse over the window */
1208         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
1209
1210         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
1211
1212         xcb_property_handlers_init(&prophs, &evenths);
1213         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
1214
1215         root = xcb_aux_get_screen(c, screens)->root;
1216         root_win = root;
1217
1218         uint32_t mask = XCB_CW_EVENT_MASK;
1219         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE };
1220         xcb_change_window_attributes(c, root, mask, values);
1221
1222         #define BIND(key, modifier, cmd) { \
1223                 Binding *new = malloc(sizeof(Binding)); \
1224                 new->keycode = key; \
1225                 new->mods = modifier; \
1226                 new->command = cmd; \
1227                 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
1228         }
1229
1230         /* 38 = 'a' */
1231         BIND(38, BIND_MODE_SWITCH, "foo");
1232
1233         BIND(30, 0, "exec /usr/pkg/bin/urxvt");
1234
1235         BIND(44, BIND_MOD_1, "h");
1236         BIND(45, BIND_MOD_1, "j");
1237         BIND(46, BIND_MOD_1, "k");
1238         BIND(47, BIND_MOD_1, "l");
1239
1240         BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh");
1241         BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj");
1242         BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk");
1243         BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl");
1244
1245         BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh");
1246         BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj");
1247         BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk");
1248         BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml");
1249
1250         Binding *bind;
1251         TAILQ_FOREACH(bind, &bindings, bindings) {
1252                 printf("Grabbing %d\n", bind->keycode);
1253                 if (bind->mods & BIND_MODE_SWITCH)
1254                         xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
1255                 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1256         }
1257
1258         start_application(TERMINAL, NULL);
1259
1260         xcb_flush(c);
1261
1262         manage_existing_windows(c, &prophs, root);
1263
1264         xcb_event_wait_for_event_loop(&evenths);
1265
1266         /* not reached */
1267         return 0;
1268 }