]> git.sur5r.net Git - i3/i3/blob - mainx.c
Implement keybindings, adjust CMDMODE grammar, update DEPENDS
[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         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
352         i3Font *font = load_font(conn, pattern);
353         uint32_t background_color,
354                  text_color,
355                  border_color;
356
357         if (client->container->currently_focused == client) {
358                 background_color = get_colorpixel(conn, client->frame, "#285577");
359                 text_color = get_colorpixel(conn, client->frame, "#ffffff");
360                 border_color = get_colorpixel(conn, client->frame, "#4c7899");
361         } else {
362                 background_color = get_colorpixel(conn, client->frame, "#222222");
363                 text_color = get_colorpixel(conn, client->frame, "#888888");
364                 border_color = get_colorpixel(conn, client->frame, "#333333");
365         }
366
367         /* Our plan is the following:
368            - Draw a rect around the whole client in background_color
369            - Draw two lines in a lighter color
370            - Draw the window’s title
371
372            Note that xcb_image_text apparently adds 1xp border around the font? Can anyone confirm this?
373          */
374
375         /* Draw a green rectangle around the window */
376         mask = XCB_GC_FOREGROUND;
377         values[0] = background_color;
378         xcb_change_gc(conn, client->titlegc, mask, values);
379
380         xcb_rectangle_t rect = {0, 0, client->width, client->height};
381         xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &rect);
382
383         /* Draw the lines */
384         /* TODO: this needs to be more beautiful somewhen. maybe stdarg + change_gc(gc, ...) ? */
385 #define DRAW_LINE(colorpixel, x, y, to_x, to_y) { \
386                 uint32_t draw_values[1]; \
387                 draw_values[0] = colorpixel; \
388                 xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND, draw_values); \
389                 xcb_point_t points[] = {{x, y}, {to_x, to_y}}; \
390                 xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 2, points); \
391         }
392
393         DRAW_LINE(border_color, 2, 0, client->width, 0);
394         DRAW_LINE(border_color, 2, font->height + 3, 2 + client->width, font->height + 3);
395
396         /* Draw the font */
397         mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
398
399         values[0] = text_color;
400         values[1] = background_color;
401         values[2] = font->id;
402
403         xcb_change_gc(conn, client->titlegc, mask, values);
404
405         /* TODO: utf8? */
406         char *label;
407         asprintf(&label, "gots win %08x", client->frame);
408         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), client->frame,
409                                         client->titlegc, 3 /* X */, font->height /* Y = baseline of font */, label);
410         free(label);
411 }
412
413 void render_container(xcb_connection_t *connection, Container *container) {
414         Client *client;
415         uint32_t values[4];
416         uint32_t mask = XCB_CONFIG_WINDOW_X |
417                         XCB_CONFIG_WINDOW_Y |
418                         XCB_CONFIG_WINDOW_WIDTH |
419                         XCB_CONFIG_WINDOW_HEIGHT;
420         i3Font *font = load_font(connection, pattern);
421
422         if (container->mode == MODE_DEFAULT) {
423                 int num_clients = 0;
424                 CIRCLEQ_FOREACH(client, &(container->clients), clients)
425                         num_clients++;
426                 printf("got %d clients in this default container.\n", num_clients);
427
428                 int current_client = 0;
429                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
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                         /* TODO: vertical default layout */
438                         values[2] = container->width; /* width */
439                         values[3] = container->height / num_clients; /* height */
440                         printf("frame will be at %dx%d with size %dx%d\n",
441                                         values[0], values[1], values[2], values[3]);
442
443                         client->width = values[2];
444                         client->height = values[3];
445
446                         /* TODO: update only if necessary */
447                         xcb_configure_window(connection, client->frame, mask, values);
448
449                         /* The coordinates of the child are relative to its frame, we
450                          * add a border of 2 pixel to each value */
451                         values[0] = 2;
452                         values[1] = font->height + 2 + 2;
453                         values[2] -= values[0] + 2;
454                         values[3] -= values[1] + 2;
455                         printf("child itself will be at %dx%d with size %dx%d\n",
456                                         values[0], values[1], values[2], values[3]);
457
458                         xcb_configure_window(connection, client->child, mask, values);
459
460                         decorate_window(connection, client);
461                         current_client++;
462                 }
463         } else {
464                 /* TODO: Implement stacking */
465         }
466 }
467
468 void render_layout(xcb_connection_t *conn) {
469         int cols, rows;
470         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
471         int width = root_screen->width_in_pixels;
472         int height = root_screen->height_in_pixels;
473
474         int num_cols = table_dims.x, num_rows = table_dims.y;
475
476         printf("got %d rows and %d cols\n", num_rows, num_cols);
477         printf("each of them therefore is %d px width and %d px height\n",
478                         width / num_cols, height / num_rows);
479
480         /* Go through the whole table and render what’s necessary */
481         for (cols = 0; cols < table_dims.x; cols++)
482                 for (rows = 0; rows < table_dims.y; rows++)
483                         if (table[cols][rows] != NULL) {
484                                 Container *con = table[cols][rows];
485                                 printf("container has %d colspan, %d rowspan\n",
486                                                 con->colspan, con->rowspan);
487                                 /* Update position of the container */
488                                 con->row = rows;
489                                 con->col = cols;
490                                 con->width = (width / num_cols) * con->colspan;
491                                 con->height = (height / num_rows) * con->rowspan;
492
493                                 /* Render it */
494                                 render_container(conn, table[cols][rows]);
495                         }
496
497         xcb_flush(conn);
498 }
499
500 /*
501  * Let’s own this window…
502  *
503  */
504 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
505                 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
506                 int16_t x, int16_t y, uint16_t width, uint16_t height) {
507
508         Client *new = table_get(byChild, child);
509         if (new == NULL) {
510                 printf("oh, it's new\n");
511                 new = calloc(sizeof(Client), 1);
512         }
513         uint32_t mask = 0;
514         uint32_t values[3];
515         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
516
517         /* Insert into the currently active container */
518         CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
519
520         printf("currently_focused = %p\n", new);
521         CUR_CELL->currently_focused = new;
522         new->container = CUR_CELL;
523
524         new->frame = xcb_generate_id(conn);
525         new->child = child;
526         new->width = width;
527         new->height = height;
528
529         /* TODO: what do these mean? */
530         mask |= XCB_CW_BACK_PIXEL;
531         values[0] = root_screen->white_pixel;
532
533         mask |= XCB_CW_OVERRIDE_REDIRECT;
534         values[1] = 1;
535
536         mask |= XCB_CW_EVENT_MASK;
537         values[2] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE
538                 | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_ENTER_WINDOW;
539
540         printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
541
542         /* Yo dawg, I heard you like windows, so I create a window around your window… */
543         xcb_create_window(conn,
544                         depth,
545                         new->frame,
546                         root,
547                         x,
548                         y,
549                         width + LEFT + RIGHT,
550                         height + TOP + BOTTOM,
551                         /* border_width */ 0,
552                         XCB_WINDOW_CLASS_INPUT_OUTPUT,
553                         visual,
554                         mask,
555                         values);
556         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
557
558         /* Map the window on the screen (= make it visible) */
559         xcb_map_window(conn, new->frame);
560
561         /* Generate a graphics context for the titlebar */
562         new->titlegc = xcb_generate_id(conn);
563         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
564
565         /* Draw decorations */
566         decorate_window(conn, new);
567
568         /* Put our data structure (Client) into the table */
569         table_put(byParent, new->frame, new);
570         table_put(byChild, child, new);
571
572         /* Moves the original window into the new frame we've created for it */
573         i3Font *font = load_font(conn, pattern);
574         xcb_reparent_window(conn, child, new->frame, 0, font->height);
575
576         /* We are interested in property changes */
577         mask = XCB_CW_EVENT_MASK;
578         values[0] =     XCB_EVENT_MASK_PROPERTY_CHANGE |
579                         XCB_EVENT_MASK_STRUCTURE_NOTIFY |
580                         XCB_EVENT_MASK_ENTER_WINDOW;
581         xcb_change_window_attributes(conn, child, mask, values);
582
583         /* TODO: At the moment, new windows just get focus */
584         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_NONE, new->frame, XCB_CURRENT_TIME);
585
586         render_layout(conn);
587
588         xcb_flush(conn);
589 }
590
591 static bool focus_window_in_container(xcb_connection_t *connection, Container *container,
592                 direction_t direction) {
593         /* If this container is empty, we’re done */
594         if (container->currently_focused == NULL)
595                 return false;
596
597         Client *candidad;
598         if (direction == D_UP)
599                 candidad = CIRCLEQ_PREV(container->currently_focused, clients);
600         else if (direction == D_DOWN)
601                 candidad = CIRCLEQ_NEXT(container->currently_focused, clients);
602
603         /* If we don’t have anything to select, we’re done */
604         if (candidad == CIRCLEQ_END(&(container->clients)))
605                 return false;
606
607         /* Set focus if we could successfully move */
608         container->currently_focused = candidad;
609         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE, candidad->child, XCB_CURRENT_TIME);
610         render_layout(connection);
611         xcb_flush(connection);
612
613         return true;
614 }
615
616 static void focus_window(xcb_connection_t *connection, direction_t direction) {
617         printf("focusing direction %d\n", direction);
618         /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
619         if (direction == D_UP || direction == D_DOWN) {
620                 /* Let’s see if we can perform up/down focus in the current container */
621                 Container *container = CUR_CELL;
622
623                 /* There always is a container. If not, current_col or current_row is wrong */
624                 assert(container != NULL);
625
626                 if (focus_window_in_container(connection, container, direction))
627                         return;
628         } else if (direction == D_LEFT || direction == D_RIGHT) {
629                 if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
630                         current_col++;
631                 else if (direction == D_LEFT && cell_exists(current_col-1, current_row))
632                         current_col--;
633                 else {
634                         printf("nah, not possible\n");
635                         return;
636                 }
637                 if (CUR_CELL->currently_focused != NULL) {
638                         xcb_set_input_focus(connection, XCB_INPUT_FOCUS_NONE,
639                                         CUR_CELL->currently_focused->child, XCB_CURRENT_TIME);
640                         render_layout(connection);
641                         xcb_flush(connection);
642                 }
643
644         } else {
645                 printf("direction unhandled\n");
646         }
647 }
648
649 /*
650  * Tries to move the window inside its current container.
651  *
652  * Returns true if the window could be moved, false otherwise.
653  *
654  */
655 static bool move_current_window_in_container(xcb_connection_t *connection, Client *client,
656                 direction_t direction) {
657         Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) :
658                                                 CIRCLEQ_NEXT(client, clients));
659
660         if (other == CIRCLEQ_END(&(client->container->clients)))
661                 return false;
662
663         printf("i can do that\n");
664         /* We can move the client inside its current container */
665         CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
666         if (direction == D_UP)
667                 CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients);
668         else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients);
669         render_layout(connection);
670         return true;
671 }
672
673 /*
674  * Moves the current window to the given direction, creating a column/row if
675  * necessary
676  *
677  */
678 static void move_current_window(xcb_connection_t *connection, direction_t direction) {
679         printf("moving window to direction %d\n", direction);
680         /* Get current window */
681         Container *container = CUR_CELL,
682                   *new;
683
684         /* There has to be a container, see focus_window() */
685         assert(container != NULL);
686
687         /* If there is no window, we’re done */
688         if (container->currently_focused == NULL)
689                 return;
690
691         /* As soon as the client is moved away, the next client in the old
692          * container needs to get focus, if any. Therefore, we save it here. */
693         Client *current_client = container->currently_focused;
694         Client *to_focus = CIRCLEQ_NEXT(current_client, clients);
695         if (to_focus == CIRCLEQ_END(&(container->clients)))
696                 to_focus = NULL;
697
698         switch (direction) {
699                 case D_LEFT:
700                         if (current_col == 0)
701                                 return;
702
703                         new = table[--current_col][current_row];
704                         break;
705                 case D_RIGHT:
706                         if (current_col == (table_dims.x-1))
707                                 expand_table_cols();
708
709                         new = table[++current_col][current_row];
710                         break;
711                 case D_UP:
712                         if (move_current_window_in_container(connection, current_client, D_UP) ||
713                                 current_row == 0)
714                                 return;
715
716                         new = table[current_col][--current_row];
717                         break;
718                 case D_DOWN:
719                         if (move_current_window_in_container(connection, current_client, D_DOWN))
720                                 return;
721
722                         if (current_row == (table_dims.y-1))
723                                 expand_table_rows();
724
725                         new = table[current_col][++current_row];
726                         break;
727         }
728
729         /* Remove it from the old container and put it into the new one */
730         CIRCLEQ_REMOVE(&(container->clients), current_client, clients);
731         CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients);
732
733         /* Update data structures */
734         current_client->container = new;
735         container->currently_focused = to_focus;
736         new->currently_focused = current_client;
737
738         /* TODO: delete all empty columns/rows */
739
740         render_layout(connection);
741 }
742
743 int format_event(xcb_generic_event_t *e)
744 {           
745     uint8_t sendEvent;
746     uint16_t seqnum;
747
748     sendEvent = (e->response_type & 0x80) ? 1 : 0;
749     e->response_type &= ~0x80;
750     seqnum = *((uint16_t *) e + 1);
751
752     switch(e->response_type) 
753     {   
754     case 0:
755         printf("Error %s on seqnum %d (%s).\n",
756             labelError[*((uint8_t *) e + 1)],
757             seqnum,
758             labelRequest[*((uint8_t *) e + 10)]);
759         break;
760     default:
761         printf("Event %s following seqnum %d%s.\n",
762             labelEvent[e->response_type],
763             seqnum,
764             labelSendEvent[sendEvent]);
765         break;  
766     case XCB_KEYMAP_NOTIFY:
767         printf("Event %s%s.\n",
768             labelEvent[e->response_type],
769             labelSendEvent[sendEvent]);
770         break;
771     }
772
773     fflush(stdout);
774     return 1;
775 }
776
777
778 static int handleEvent(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e)
779 {
780         return format_event(e);
781 }
782
783 /*
784  * Starts the given application with the given args.
785  *
786  */
787 static void start_application(char *path, char *args) {
788         pid_t pid;
789         if ((pid = vfork()) == 0) {
790                 /* This is the child */
791                 char *argv[2];
792                 /* TODO: For now, we ignore args. Later on, they should be parsed
793                    correctly (like in the shell?) */
794                 argv[0] = path;
795                 argv[1] = NULL;
796                 execve(path, argv, environment);
797                 /* not reached */
798         }
799 }
800
801 /*
802  * Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
803  * Therefore, we just replay all key presses.
804  *
805  */
806 static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
807         printf("got key release, just passing\n");
808         xcb_allow_events(conn, ReplayKeyboard, event->time);
809         xcb_flush(conn);
810         return 1;
811 }
812
813 /*
814  * Parses a command, see file CMDMODE for more information
815  *
816  */
817 static void parse_command(xcb_connection_t *conn, const char *command) {
818         printf("--- parsing command \"%s\" ---\n", command);
819         /* Hmm, just to be sure */
820         if (command[0] == '\0')
821                 return;
822
823         /* Is it an <exec>? */
824         if (strncmp(command, "exec ", strlen("exec ")) == 0) {
825                 printf("starting \"%s\"\n", command + strlen("exec "));
826                 start_application(command+strlen("exec "), NULL);
827                 return;
828         }
829
830         /* Is it a <with>? */
831         if (command[0] == 'w') {
832                 /* TODO: implement */
833                 printf("not yet implemented.\n");
834                 return;
835         }
836
837         /* It's a normal <cmd> */
838         int times;
839         char *rest = NULL;
840         enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
841         direction_t direction;
842         times = strtol(command, &rest, 10);
843         if (rest == NULL) {
844                 printf("Invalid command: Consists only of a movement\n");
845                 return;
846         }
847         if (*rest == 'm' || *rest == 's') {
848                 action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP);
849                 rest++;
850         }
851
852         /* Now perform action to <where> */
853         while (*rest != '\0') {
854                 /* TODO: tags */
855                 if (*rest == 'h')
856                         direction = D_LEFT;
857                 else if (*rest == 'j')
858                         direction = D_DOWN;
859                 else if (*rest == 'k')
860                         direction = D_UP;
861                 else if (*rest == 'l')
862                         direction = D_RIGHT;
863                 else {
864                         printf("unknown direction: %c\n", *rest);
865                         return;
866                 }
867
868                 if (action == ACTION_FOCUS)
869                         focus_window(conn, direction);
870                 else if (action == ACTION_MOVE)
871                         move_current_window(conn, direction);
872                 else if (action == ACTION_SNAP)
873                         /* TODO: implement */
874                         printf("snap not yet implemented\n");
875
876                 rest++;
877
878         }
879
880         printf("--- done ---\n");
881 }
882
883 /*
884  * There was a key press. We lookup the key symbol and see if there are any bindings
885  * on that. This allows to do things like binding special characters (think of Ã¤) to
886  * functions to get one more modifier while not losing AltGr :-)
887  * TODO: this description needs to be more understandable
888  *
889  */
890 static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
891         printf("Keypress %d\n", event->detail);
892
893         /* We need to get the keysym group (There are group 1 to group 4, each holding
894            two keysyms (without shift and with shift) using Xkb because X fails to
895            provide them reliably (it works in Xephyr, it does not in real X) */
896         XkbStateRec state;
897         if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
898                 event->state |= 0x2;
899
900         printf("state %d\n", event->state);
901
902         /* Find the binding */
903         Binding *bind, *best_match = TAILQ_END(&bindings);
904         TAILQ_FOREACH(bind, &bindings, bindings) {
905                 if (bind->keycode == event->detail &&
906                         (bind->mods & event->state) == bind->mods) {
907                         if (best_match == TAILQ_END(&bindings) ||
908                                 bind->mods > best_match->mods)
909                                 best_match = bind;
910                 }
911         }
912
913         if (best_match == TAILQ_END(&bindings)) {
914                 printf("This key was not bound by us?! (most likely a bug)\n");
915                 return 1; /* TODO: return 0? what do the codes mean? */
916         }
917
918         if (event->state & 0x2) {
919                 printf("that's mode_switch\n");
920                 parse_command(conn, best_match->command);
921                 printf("ok, hiding this event.\n");
922                 xcb_allow_events(conn, SyncKeyboard, event->time);
923                 xcb_flush(conn);
924                 return 1;
925         }
926
927         /* If this was an actively grabbed key, and we did not handle it, we need to pass it */
928         if (best_match->mods & BIND_MODE_SWITCH) {
929                 printf("passing...\n");
930                 xcb_allow_events(conn, ReplayKeyboard, event->time);
931                 xcb_flush(conn);
932                 return 1;
933         }
934
935         parse_command(conn, best_match->command);
936         return 1;
937 }
938
939 /*
940  * When the user moves the mouse pointer onto a window, this callback gets called.
941  *
942  */
943 static int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
944         /* This was either a focus for a client’s parent (= titlebar)… */
945         Client *client = table_get(byParent, event->event),
946                *old_client;
947         /* â€¦or the client itself */
948         if (client == NULL)
949                 client = table_get(byChild, event->event);
950
951         /* If not, then this event is not interesting. This should not happen */
952         if (client == NULL) {
953                 printf("DEBUG: Uninteresting enter_notify-event?\n");
954                 return 1;
955         }
956
957         /* Update container */
958         old_client = client->container->currently_focused;
959         client->container->currently_focused = client;
960
961         current_col = client->container->col;
962         current_row = client->container->row;
963
964         /* Set focus to the entered window, and flush xcb buffer immediately */
965         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
966         /* Update last/current client’s titlebar */
967         if (old_client != NULL)
968                 decorate_window(conn, old_client);
969         decorate_window(conn, client);
970         xcb_flush(conn);
971
972         return 1;
973 }
974
975
976 int handle_map_notify_event(void *prophs, xcb_connection_t *c, xcb_map_notify_event_t *e)
977 {
978         window_attributes_t wa = { TAG_VALUE };
979         wa.u.override_redirect = e->override_redirect;
980         printf("MapNotify for 0x%08x.\n", e->window);
981         manage_window(prophs, c, e->window, wa);
982         return 1;
983 }
984
985 /*
986  * Our window decorations were unmapped. That means, the window will be killed now,
987  * so we better clean up before.
988  *
989  */
990 int handle_unmap_notify_event(void *data, xcb_connection_t *c, xcb_unmap_notify_event_t *e) {
991         Client *client = table_remove(byChild, e->event);
992         xcb_window_t root;
993         printf("UnmapNotify for 0x%08x (received from 0x%08x): ", e->window, e->event);
994         if(!client)
995         {
996                 printf("not a managed window. Ignoring.\n");
997                 return 0;
998         }
999
1000         int rows, cols;
1001         Client *con_client;
1002         for (cols = 0; cols < table_dims.x; cols++)
1003                 for (rows = 0; rows < table_dims.y; rows++)
1004                         CIRCLEQ_FOREACH(con_client, &(table[cols][rows]->clients), clients)
1005                                 if (con_client == client) {
1006                                         printf("removing from container\n");
1007                                         if (client->container->currently_focused == client)
1008                                                 client->container->currently_focused = NULL;
1009                                         CIRCLEQ_REMOVE(&(table[cols][rows]->clients), con_client, clients);
1010                                         break;
1011                                 }
1012
1013
1014
1015         root = xcb_setup_roots_iterator(xcb_get_setup(c)).data->root;
1016         printf("child of 0x%08x.\n", client->frame);
1017         xcb_reparent_window(c, client->child, root, 0, 0);
1018         xcb_destroy_window(c, client->frame);
1019         xcb_flush(c);
1020         table_remove(byParent, client->frame);
1021         free(client);
1022
1023         render_layout(c);
1024
1025         return 1;
1026 }
1027
1028
1029
1030 static int handleExposeEvent(void *data, xcb_connection_t *c, xcb_expose_event_t *e) {
1031 printf("exposeevent\n");
1032         Client *client = table_get(byParent, e->window);
1033         if(!client || e->count != 0)
1034                 return 1;
1035         decorate_window(c, client);
1036         return 1;
1037 }
1038 void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) {
1039         xcb_query_tree_cookie_t wintree;
1040         xcb_query_tree_reply_t *rep;
1041         int i, len;
1042         xcb_window_t *children;
1043         xcb_get_window_attributes_cookie_t *cookies;
1044
1045         wintree = xcb_query_tree(c, root);
1046         rep = xcb_query_tree_reply(c, wintree, 0);
1047         if(!rep)
1048                 return;
1049         len = xcb_query_tree_children_length(rep);
1050         cookies = malloc(len * sizeof(*cookies));
1051         if(!cookies)
1052         {
1053                 free(rep);
1054                 return;
1055         }
1056         children = xcb_query_tree_children(rep);
1057         for(i = 0; i < len; ++i)
1058                 cookies[i] = xcb_get_window_attributes(c, children[i]);
1059         for(i = 0; i < len; ++i)
1060         {
1061                 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
1062                 manage_window(prophs, c, children[i], wa);
1063         }
1064         free(rep);
1065 }
1066
1067 int main(int argc, char *argv[], char *env[]) {
1068         int i, e = 0;
1069
1070         for (i = 0; (env[i] != NULL); i++)
1071                 if (strncmp(env[i], "LC_", strlen("LC_")) == 0 ||
1072                         strncmp(env[i], "LANG=", strlen("LANG=")) == 0 ||
1073                         strncmp(env[i], "DISPLAY=", strlen("DISPLAY=")) == 0) {
1074                         printf("Passing environment \"%s\"\n", env[i]);
1075                         environment = realloc(environment, sizeof(char*) * ++e);
1076                         environment[e-1] = env[i];
1077                 }
1078
1079         /* environment has to be NULL-terminated */
1080         environment = realloc(environment, sizeof(char*) * ++e);
1081         environment[e-1] = NULL;
1082
1083         init_table();
1084
1085         xcb_connection_t *c;
1086         xcb_event_handlers_t evenths;
1087         xcb_property_handlers_t prophs;
1088         xcb_window_t root;
1089
1090         int screens;
1091
1092         memset(&evenths, 0, sizeof(xcb_event_handlers_t));
1093         memset(&prophs, 0, sizeof(xcb_property_handlers_t));
1094
1095         byChild = alloc_table();
1096         byParent = alloc_table();
1097
1098         TAILQ_INIT(&bindings);
1099
1100         c = xcb_connect(NULL, &screens);
1101
1102         printf("x screen is %d\n", screens);
1103
1104         /* TODO: this has to be more beautiful somewhen */
1105         int major, minor, error;
1106
1107         major = XkbMajorVersion;
1108         minor = XkbMinorVersion;
1109
1110         int evBase, errBase;
1111
1112         if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
1113                 fprintf(stderr, "XkbOpenDisplay() failed\n");
1114                 return 1;
1115         }
1116
1117         int i1;
1118         if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
1119                 fprintf(stderr, "XKB not supported by X-server\n");
1120                 return 1;
1121         }
1122         /* end of ugliness */
1123
1124         xcb_event_handlers_init(c, &evenths);
1125         for(i = 2; i < 128; ++i)
1126                 xcb_event_set_handler(&evenths, i, handleEvent, 0);
1127
1128         for(i = 0; i < 256; ++i)
1129                 xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t) handleEvent, 0);
1130
1131         /* Expose = an Application should redraw itself. That is, we have to redraw our
1132          * contents (= top/bottom bar, titlebars for each window) */
1133         xcb_event_set_expose_handler(&evenths, handleExposeEvent, 0);
1134
1135         /* Key presses/releases are pretty obvious, I think */
1136         xcb_event_set_key_press_handler(&evenths, handle_key_press, 0);
1137         xcb_event_set_key_release_handler(&evenths, handle_key_release, 0);
1138
1139         /* Enter window = user moved his mouse over the window */
1140         xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0);
1141
1142         xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0);
1143
1144         xcb_property_handlers_init(&prophs, &evenths);
1145         xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs);
1146
1147         root = xcb_aux_get_screen(c, screens)->root;
1148         root_win = root;
1149
1150         uint32_t mask = XCB_CW_EVENT_MASK;
1151         uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE };
1152         xcb_change_window_attributes(c, root, mask, values);
1153
1154         #define BIND(key, modifier, cmd) { \
1155                 Binding *new = malloc(sizeof(Binding)); \
1156                 new->keycode = key; \
1157                 new->mods = modifier; \
1158                 new->command = cmd; \
1159                 TAILQ_INSERT_TAIL(&bindings, new, bindings); \
1160         }
1161
1162         /* 38 = 'a' */
1163         BIND(38, BIND_MODE_SWITCH, "foo");
1164
1165         BIND(30, 0, "exec /usr/pkg/bin/urxvt");
1166
1167         BIND(44, BIND_MOD_1, "h");
1168         BIND(45, BIND_MOD_1, "j");
1169         BIND(46, BIND_MOD_1, "k");
1170         BIND(47, BIND_MOD_1, "l");
1171
1172         BIND(44, BIND_MOD_1 | BIND_CONTROL, "mh");
1173         BIND(45, BIND_MOD_1 | BIND_CONTROL, "mj");
1174         BIND(46, BIND_MOD_1 | BIND_CONTROL, "mk");
1175         BIND(47, BIND_MOD_1 | BIND_CONTROL, "ml");
1176
1177         Binding *bind;
1178         TAILQ_FOREACH(bind, &bindings, bindings) {
1179                 printf("Grabbing %d\n", bind->keycode);
1180                 if (bind->mods & BIND_MODE_SWITCH)
1181                         xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
1182                 else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC);
1183         }
1184
1185         start_application(TERMINAL, NULL);
1186
1187         xcb_flush(c);
1188
1189         manage_existing_windows(c, &prophs, root);
1190
1191         xcb_event_wait_for_event_loop(&evenths);
1192
1193         /* not reached */
1194         return 0;
1195 }